blob: ecd3aaf21fd8fd2cc3d4f4bb67b79cabf1131c99 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ZXTEST_BASE_VALUES_H_
#define ZXTEST_BASE_VALUES_H_
#include <lib/fit/function.h>
#include <lib/stdcompat/array.h>
#include <lib/stdcompat/span.h>
#include <math.h>
#include <optional>
#include <tuple>
#include <type_traits>
#include <vector>
#include <zxtest/base/test.h>
namespace zxtest {
namespace internal {
// This class takes provides a container interface but is based on taking a lambda that owns the
// storage of the parameters and provides access to them. The contained parameters are immutable.
template <typename T>
class ValueProvider {
public:
using ValueType = std::remove_cv_t<T>;
using ConstRef = const ValueType&;
using Callback = typename fit::function<ConstRef(size_t)>;
ValueProvider() = delete;
ValueProvider(Callback accessor, size_t size) : accessor_(std::move(accessor)), size_(size) {}
template <typename U,
std::enable_if_t<(std::is_convertible_v<U, T> || std::is_constructible_v<T, U>)>>
explicit ValueProvider(ValueProvider<U>&& provider)
: size_(provider.size()),
accessor_([provider = std::move(provider)](size_t i) -> const T& { return provider[i]; }) {}
ValueProvider(const ValueProvider&) = delete;
ValueProvider(ValueProvider&&) noexcept = default;
template <typename U, typename std::enable_if_t<
(std::is_convertible_v<U, T> ||
std::is_constructible_v<T, U>)&&!std::is_same_v<U, T>>* = nullptr>
ValueProvider(ValueProvider<U>&& other)
: accessor_([cb = std::move(other.accessor_)](size_t index) -> const T& {
static std::optional<cpp20::remove_cvref_t<T>> tmp;
tmp.emplace(cb(index));
return tmp.value();
}),
size_(other.size_) {}
ValueProvider& operator=(ValueProvider&&) noexcept = default;
~ValueProvider() = default;
ConstRef operator[](size_t index) const {
ZX_ASSERT_MSG(index < size_, "Out of range.");
return accessor_(index);
}
size_t size() const { return size_; }
private:
template <typename U>
friend class ValueProvider;
Callback accessor_ = nullptr;
size_t size_ = 0;
};
} // namespace internal
namespace internal {
// NOTE about `Combine`:
// We handle more than two parameters through recursion. The leftmost two parameters are combined
// into a tuple at each recursive call, and the tuples are merged using `std::tuple_cat`. However,
// if the user wants to pass in a tuple parameter, that must be handled like a regular value and not
// be concatenated using `std::tuple_cat`. The combination of `Combine` and `internal::Combine`
// functions handle this distinction.
// Internal combine handler.
// See above for a note about why there are internal handlers.
template <typename... A, typename B>
auto Combine(::zxtest::internal::ValueProvider<std::tuple<A...>> a,
::zxtest::internal::ValueProvider<B> b) {
using ParamType = decltype(std::tuple_cat(a[0], std::tuple(b[0])));
size_t total_elements = a.size() * b.size();
auto values = [a = std::move(a), b = std::move(b)](size_t index) -> ParamType& {
size_t a_index = index / b.size();
size_t b_index = index % b.size();
static ParamType storage;
storage = std::tuple_cat(a[a_index], std::tuple(b[b_index]));
return storage;
};
return ::zxtest::internal::ValueProvider<ParamType>(
[a = std::move(values)](size_t index) -> const ParamType& { return a(index); },
total_elements);
}
// Internal combine handler.
// See above for a note about why there are internal handlers.
template <typename... A, typename... B>
auto Combine(::zxtest::internal::ValueProvider<std::tuple<A...>> a,
::zxtest::internal::ValueProvider<std::tuple<B...>> b) {
using ParamType = decltype(std::tuple_cat(a[0], std::make_tuple(b[0])));
size_t total_elements = a.size() * b.size();
auto values = [a = std::move(a), b = std::move(b)](size_t index) -> ParamType& {
size_t a_index = index / b.size();
size_t b_index = index % b.size();
static ParamType storage;
storage = std::tuple_cat(a[a_index], std::make_tuple(b[b_index]));
return storage;
};
return ::zxtest::internal::ValueProvider<ParamType>(
[a = std::move(values)](size_t index) -> const ParamType& { return a(index); },
total_elements);
}
// Internal combine handler.
// See above for a note about why there are internal handlers.
template <typename A, typename B, typename... ProviderType>
auto Combine(::zxtest::internal::ValueProvider<A> a, ::zxtest::internal::ValueProvider<B> b,
ProviderType&&... providers) {
return internal::Combine(std::move(internal::Combine(std::move(a), std::move(b))),
std::forward<ProviderType>(providers)...);
}
} // namespace internal
// Combines two ValueProviders producing a ValueProvider with a cartesian product of both.
template <typename A, typename B>
auto Combine(::zxtest::internal::ValueProvider<A> a, ::zxtest::internal::ValueProvider<B> b) {
using ParamType = typename std::tuple<A, B>;
size_t total_elements = a.size() * b.size();
auto values = [a = std::move(a), b = std::move(b)](size_t index) -> ParamType& {
size_t a_index = index / b.size();
size_t b_index = index % b.size();
static ParamType storage;
storage = std::tuple(a[a_index], b[b_index]);
return storage;
};
return ::zxtest::internal::ValueProvider<ParamType>(
[a = std::move(values)](size_t index) -> const ParamType& { return a(index); },
total_elements);
}
// Combines more than two ValueProviders.
template <typename A, typename B, typename... ProviderType>
auto Combine(::zxtest::internal::ValueProvider<A> a, ::zxtest::internal::ValueProvider<B> b,
ProviderType&&... providers) {
return internal::Combine(std::move(zxtest::Combine(std::move(a), std::move(b))),
std::forward<ProviderType>(providers)...);
}
// Takes in a container of values and returns a ValueProvider for those values.
// Supports containers that support ::value_type and iterator.
template <typename C>
auto ValuesIn(const C& values) {
using ParamType = typename C::value_type;
size_t size = values.size();
return ::zxtest::internal::ValueProvider<ParamType>(
[values = std::move(values)](size_t index) -> const ParamType& {
return *(values.begin() + index);
},
size);
}
// Takes in values as parameters and returns a ValueProvider for those values.
template <typename... Args>
auto Values(Args... args) {
using ParamType = typename std::common_type<Args...>::type;
auto values = cpp20::to_array<ParamType>({args...});
return ValuesIn(values);
}
// Generates a series of values according to the parameters given.
// Increments by `step` starting from `start`, ending before `end`.
// The `end` value is excluded.
template <typename A, typename B, typename T>
auto Range(A start, B end, T step) {
static_assert((std::is_same_v<A, B>), "`start` and `end` parameters must be of equal type.");
ZX_ASSERT_MSG(start < end, "`start` must be less than `end`.");
A gap = end - start;
size_t size = static_cast<size_t>(ceil(static_cast<double>(gap) / static_cast<double>(step)));
return ::zxtest::internal::ValueProvider<A>(
[start = std::move(start), step = std::move(step)](size_t index) -> const A& {
static A result;
// Cast to `A` is safe because `index` is already enforced to be smaller than `size` in the
// `ValueProvider`.
result = start + (step * static_cast<A>(index));
return result;
},
size);
}
template <typename A, typename B>
auto Range(A start, B end) {
return Range(start, end, 1);
}
// Helper function to return a ValueProvider that has both bool values.
static inline auto Bool() { return Values(false, true); }
} // namespace zxtest
#endif // ZXTEST_BASE_VALUES_H_