Skip to content

Commit

Permalink
[pd_controller] Refactor utils functions into Utils.hpp
Browse files Browse the repository at this point in the history
Rationale: May be used in other part of the test code later on
  • Loading branch information
ArthurVal committed Dec 13, 2024
1 parent 8b2d0b5 commit 720fd61
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 58 deletions.
246 changes: 246 additions & 0 deletions tests/Utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#pragma once

#include <array>
#include <ostream>
#include <type_traits>
#include <utility> // std::forward

namespace test::utils {

/**
* \brief Generate a std::array of variable size with a fixed type
*
* This function may be used when people want to create let the compiler
* deduces the size of an array<> based on the number of argument provided at
* construction but force the type contained within the array since it
* cast of each values provided into the ValueType.
*
* Example: https://godbolt.org/z/qE84bKrdx
*
* \tparam ValueType The array value_type needed
* \tparam ...T Deduces types of the values forwarded
*
* \param ...values Initial values used to construct the array
* \return std::array<ValueType, sizeof...(T)> The array initialized
*/
template <typename ValueType, typename... T>
constexpr auto MakeArray(T &&...values) -> std::array<ValueType, sizeof...(T)> {
return std::array<ValueType, sizeof...(T)>{
ValueType{std::forward<T>(values)}...};
}

namespace meta {

template <typename T, typename = void> struct IsStreamable : std::false_type {};

template <typename T>
struct IsStreamable<T, std::void_t<decltype(std::declval<std::ostream &>()
<< std::declval<T>())>>
: std::true_type {};

template <typename T> constexpr bool IsStreamable_v = IsStreamable<T>::value;

} // namespace meta

/**
* \brief If possible, prints \a value to \a os, otherwise prints \a backup
*
* \param[in] value Value we wish to print
* \param[inout] os Output stream we wish to output \a value in
* \param[in] backup Backup string used when value is not streamable
*
* \return std::ostream& The ostream used
*/
template <typename T>
constexpr auto
TryToPrintTo(T &&value, std::ostream &os,
std::string_view backup = "<Not Printable>") -> void {

if constexpr (meta::IsStreamable_v<std::remove_cv_t<decltype(value)>>) {
os << std::forward<T>(value);
} else {
os << backup;
}
}

/**
* \brief Represents a temporary mutation of a given reference
*
* RAII (https://en.cppreference.com/w/cpp/language/raii) object use to
* automatically modify a reference when constructing the mutator and assigning
* back the old value to it when the mutator destroyed.
*
* Additionnaly, the mutator can be aborted, reseted or we can even triggers
* the revert by hand when needed through the appropriate methods.
*
* Example: https://godbolt.org/z/eTfsvvobc
*
* \tparam ValueType The underlying type of the value we wish to mutate
*/
template <typename _ValueType> struct Mutation {

/// Underlying ValueType stored inside the mutation
using ValueType = _ValueType;

/// Default Ctor (not allowed)
constexpr Mutation() = delete;

/**
* \brief Construct a Mutation from a reference and a new tmp value
*
* \tparam T Type of the temporary data assigned to \a value
*
* \param[in] value Reference to the data we wish to mutate
* \param[in] tmp Value that will mutate the data
*/
template <typename T>
constexpr Mutation(ValueType &value, T &&tmp)
: Mutation(std::addressof(value), std::forward<T>(tmp)) {}

/// Move Ctor
constexpr Mutation(Mutation &&other) {
// Use the move assignement below
*this = std::move(other);
}

/// Move Assignement
constexpr auto operator=(Mutation &&other) -> Mutation & {
ptr_ = other.ptr_;
old_value_ = std::move(other.old_value_);

other.Abort();
return *this;
}

/// Copy Ctor (not allowed)
constexpr Mutation(const Mutation &other) = delete;

/// Copy Assignement (not allowed)
constexpr Mutation &operator=(const Mutation &other) = delete;

/// Dtor that triggers the revert operation
virtual ~Mutation() { Revert(); }

/**
* \brief Reset the mutator with a new data/tmp value to look at
*
* \param[in] new_data New data to look for
* \param[in] tmp New temporary value to assign to \a new_data
* \param[in] revert True if we wish to revert the previous data before
*/
template <typename T>
constexpr auto Reset(ValueType &new_data, T &&tmp,
bool revert = true) -> void {
if (revert) {
Revert();
}

*this = Mutation{new_data, std::forward<T>(tmp)};
}

/**
* \brief Abort the mutation
*/
constexpr auto Abort() noexcept -> void { ptr_ = nullptr; }

/**
* \return True when the mutation is aborted
*/
constexpr auto IsAborted() const noexcept -> bool { return ptr_ == nullptr; }

/**
* \brief Triggers the assignement of the old value into data, if not aborted
*/
constexpr auto Revert() -> void {
if (not IsAborted()) {
*ptr_ = old_value_;
}
}

/**
* \return ValueType * const Current data ptr being watched by the mutator
*/
constexpr auto GetData() const noexcept -> ValueType *const { return ptr_; }

/**
* \return const ValueType& Old value memorized (use when reverting)
*/
constexpr auto GetOldValue() const noexcept -> const ValueType & {
return old_value_;
}

/**
* \return ValueType& Old value memorized (use when reverting)
*/
constexpr auto GetOldValue() noexcept -> ValueType & { return old_value_; }

private:
/// Private ctor (/!\ Assumes that \a ptr is NOT NULL /!\)
template <typename T>
constexpr Mutation(ValueType *ptr, T &&tmp) : ptr_(ptr), old_value_(*ptr_) {
*ptr_ = std::forward<T>(tmp);
}

ValueType *ptr_; /*!< Ptr of the data we wish to mutate/revert */
ValueType old_value_; /*!< Old value memorize before mutation */
};

/**
* \brief Helper function that creates a Mutation<>
*
* This function only serves to clarify names by adding an emphasis on the
* 'temporary' aspect of the Mutation.
*
* \tparam ValueType Underlying type of the data we wish the mutate
* \tparam T Type of the temporary assigned to \a value
*
* \param[in] value The value we wish to temporarly mutate
* \param[in] tmp The new temporary value
* \return Mutation<ValueType> Containing the mutation, reverting the changes
* when going out of scope
*/
template <typename ValueType, typename T>
constexpr auto TemporaryMutate(ValueType &value,
T &&tmp) -> Mutation<ValueType> {
return Mutation<ValueType>{value, std::forward<T>(tmp)};
}

/// Format specifier for the PrintTo function below
struct MutationPrintFormat {
unsigned show_data : 1; /*!< Print the Mutation.GetData() value */
unsigned show_old_value : 1; /*!< Print the Old value */
};

/**
* \brief Prints \a mutation to the provided \a os, given the format \a fmt
*/
template <typename ValueType>
constexpr auto PrintTo(const Mutation<ValueType> &mutation,
std::ostream *const os,
MutationPrintFormat fmt = {
.show_data = true,
.show_old_value = true,
}) -> void {
if (os != nullptr) {
*os << "Mutation{";

if (mutation.IsAborted()) {
*os << " ABORTED ";
} else {
*os << ".ptr = " << (void *const)mutation.GetData();

if (fmt.show_data) {
*os << " -> ";
TryToPrintTo(*mutation.GetData(), *os);
}

if (fmt.show_old_value) {
*os << ", .old_value = ";
TryToPrintTo(mutation.GetOldValue(), *os);
}
}

*os << "}";
}
}
} // namespace test::utils
77 changes: 19 additions & 58 deletions tests/test_pd_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,15 @@
#include <optional>

#include "gtest/gtest.h"
#include "linear_feedback_controller/pd_controller.hpp"

using namespace linear_feedback_controller;

// Generate an array of a given type with its size deduced by the number of args
//
// This may be usefull when you don't want to specify by hand the number of
// elements inside an array but want to force the value_type of the array (in
// order to force conversion & co ...)
template <typename ValueType, typename... T>
constexpr auto MakeArray(T &&...values) -> std::array<ValueType, sizeof...(T)> {
return std::array<ValueType, sizeof...(T)>{std::forward<T>(values)...};
}

// RAII mutator, use to temporary modify a value given a reference and a tmp
// value
//
// WARNING: Dangling references & co ...
template <typename ValueType>
struct TemporaryMutate {
TemporaryMutate() = delete;

template <typename T>
TemporaryMutate(ValueType &data, T &&tmp_value)
: data_(data), before_(data_) {
data_ = std::forward<T>(tmp_value);
}
#include "linear_feedback_controller/pd_controller.hpp"

virtual ~TemporaryMutate() { data_ = before_; }
#include "Utils.hpp"

constexpr auto GetOldValue() const -> const ValueType & { return before_; }
using test::utils::MakeArray;
using test::utils::TemporaryMutate;

private:
ValueType &data_;
ValueType before_;
};
using namespace linear_feedback_controller;

TEST(PdControllerTest, Ctor) {
EXPECT_NO_THROW({ const auto pd_ctrl = PDController(); });
Expand Down Expand Up @@ -79,19 +52,13 @@ TEST(PdControllerTest, SetGains) {
}) {
SCOPED_TRACE(::testing::Message() << "Special value: " << special_value);

{
SCOPED_TRACE("P");
auto _ = TemporaryMutate{requested_gains.p(0), special_value};
EXPECT_NO_THROW(
{ pd_ctrl.set_gains(requested_gains.p, requested_gains.d); });
}

{
SCOPED_TRACE("D");
auto _ = TemporaryMutate{requested_gains.d(0), special_value};
EXPECT_NO_THROW(
{ pd_ctrl.set_gains(requested_gains.p, requested_gains.d); });
}
auto mutation = TemporaryMutate(requested_gains.p(0), special_value);
EXPECT_NO_THROW(
{ pd_ctrl.set_gains(requested_gains.p, requested_gains.d); });

mutation.Reset(requested_gains.d(0), special_value);
EXPECT_NO_THROW(
{ pd_ctrl.set_gains(requested_gains.p, requested_gains.d); });
}
}

Expand Down Expand Up @@ -146,19 +113,13 @@ TEST(PdControllerTest, SetReferences) {
}) {
SCOPED_TRACE(::testing::Message() << "Special value: " << special_value);

{
SCOPED_TRACE("TAU");
auto _ = TemporaryMutate{requested_refs.tau(0), special_value};
EXPECT_NO_THROW(
{ pd_ctrl.set_reference(requested_refs.tau, requested_refs.q); });
}

{
SCOPED_TRACE("Q");
auto _ = TemporaryMutate{requested_refs.q(0), special_value};
EXPECT_NO_THROW(
{ pd_ctrl.set_reference(requested_refs.tau, requested_refs.q); });
}
auto mutation = TemporaryMutate(requested_refs.tau(0), special_value);
EXPECT_NO_THROW(
{ pd_ctrl.set_reference(requested_refs.tau, requested_refs.q); });

mutation.Reset(requested_refs.q(0), special_value);
EXPECT_NO_THROW(
{ pd_ctrl.set_reference(requested_refs.tau, requested_refs.q); });
}
}

Expand Down

0 comments on commit 720fd61

Please sign in to comment.