feat add fsm
This commit is contained in:
parent
b5e5d717a9
commit
a1ec48b1ea
@ -200,6 +200,7 @@ if(SLED_BUILD_TESTS)
|
|||||||
sled_add_test(NAME sled_config_test SRCS src/sled/config_test.cc)
|
sled_add_test(NAME sled_config_test SRCS src/sled/config_test.cc)
|
||||||
sled_add_test(NAME sled_ioc_test SRCS src/sled/ioc/ioc_test.cc)
|
sled_add_test(NAME sled_ioc_test SRCS src/sled/ioc/ioc_test.cc)
|
||||||
sled_add_test(NAME sled_inja_test SRCS src/sled/nonstd/inja_test.cc)
|
sled_add_test(NAME sled_inja_test SRCS src/sled/nonstd/inja_test.cc)
|
||||||
|
sled_add_test(NAME sled_fsm_test SRCS src/sled/nonstd/fsm_test.cc)
|
||||||
endif(SLED_BUILD_TESTS)
|
endif(SLED_BUILD_TESTS)
|
||||||
|
|
||||||
if(SLED_BUILD_FUZZ)
|
if(SLED_BUILD_FUZZ)
|
||||||
|
461
src/sled/nonstd/fsm.h
Normal file
461
src/sled/nonstd/fsm.h
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015-2021 Thomas Kemmer
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use, copy,
|
||||||
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FSMLITE_FSM_H
|
||||||
|
#define FSMLITE_FSM_H
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#if !defined(NDEBUG) && (!__GNUC__ || __EXCEPTIONS)
|
||||||
|
#include <stdexcept>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace fsmlite {
|
||||||
|
template<typename T, typename State>
|
||||||
|
class Fsm;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
#if __cplusplus >= 201703L || _MSVC_LANG >= 201703L
|
||||||
|
template<class F, class... Args>
|
||||||
|
using invoke_result_t = std::invoke_result_t<F, Args...>;
|
||||||
|
|
||||||
|
template<class F, class... Args>
|
||||||
|
using is_invocable = std::is_invocable<F, Args...>;
|
||||||
|
#elif __cplusplus >= 201103L || _MSVC_LANG >= 201103L
|
||||||
|
template<class F, class... Args>
|
||||||
|
using invoke_result_t = typename std::result_of<F && (Args && ...)>::type;
|
||||||
|
|
||||||
|
struct is_invocable_test {
|
||||||
|
struct no_type {
|
||||||
|
int a;
|
||||||
|
int b;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class... Args, class = invoke_result_t<F, Args...>>
|
||||||
|
static char test(int);
|
||||||
|
|
||||||
|
template<class, class...>
|
||||||
|
static no_type test(...);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class... Args>
|
||||||
|
using is_invocable = typename std::integral_constant<bool, sizeof(is_invocable_test::test<F, Args...>(0)) == 1>::type;
|
||||||
|
#else
|
||||||
|
#error "fsmlite requires C++11 support."
|
||||||
|
#endif
|
||||||
|
// C++11 std::forward() is in <utility>, which may not be
|
||||||
|
// present on freestanding implementations
|
||||||
|
template<class T>
|
||||||
|
constexpr T &&
|
||||||
|
forward(typename std::remove_reference<T>::type &t) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<T &&>(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
constexpr T &&
|
||||||
|
forward(typename std::remove_reference<T>::type &&t) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<T &&>(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// C++17 std::invoke() is in <functional>, which may not be
|
||||||
|
// present on freestanding implementations
|
||||||
|
template<class F, class... Args>
|
||||||
|
invoke_result_t<F, Args...>
|
||||||
|
invoke(F &&f, Args &&...args)
|
||||||
|
{
|
||||||
|
return f(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class M, class T, class T1, class... Args>
|
||||||
|
invoke_result_t<M T::*, T1, Args...>
|
||||||
|
invoke(M T::*f, T1 &&obj, Args &&...args)
|
||||||
|
{
|
||||||
|
return (obj.*f)(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use any of F(), F(Arg1), F(Arg2), F(Arg1, Arg2)
|
||||||
|
template<class F,
|
||||||
|
class Arg1,
|
||||||
|
class Arg2,
|
||||||
|
bool f1 = is_invocable<F>::value,
|
||||||
|
bool f2 = is_invocable<F, Arg1>::value,
|
||||||
|
bool f3 = is_invocable<F, Arg2>::value,
|
||||||
|
bool f4 = is_invocable<F, Arg1, Arg2>::value>
|
||||||
|
struct binary_fn_helper;
|
||||||
|
|
||||||
|
template<class F, class Arg1, class Arg2>
|
||||||
|
struct binary_fn_helper<F, Arg1, Arg2, true, false, false, false> {
|
||||||
|
using result_type = invoke_result_t<F>;
|
||||||
|
|
||||||
|
static result_type invoke(F &&f, Arg1 &&, Arg2 &&) { return detail::invoke(f); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class Arg1, class Arg2>
|
||||||
|
struct binary_fn_helper<F, Arg1, Arg2, false, true, false, false> {
|
||||||
|
using result_type = invoke_result_t<F, Arg1>;
|
||||||
|
|
||||||
|
static result_type invoke(F &&f, Arg1 &&a, Arg2 &&) { return detail::invoke(f, a); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class Arg1, class Arg2>
|
||||||
|
struct binary_fn_helper<F, Arg1, Arg2, false, false, true, false> {
|
||||||
|
using result_type = invoke_result_t<F, Arg2>;
|
||||||
|
|
||||||
|
static result_type invoke(F &&f, Arg1 &&, Arg2 &&b) { return detail::invoke(f, b); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class Arg1, class Arg2>
|
||||||
|
struct binary_fn_helper<F, Arg1, Arg2, false, false, false, true> {
|
||||||
|
using result_type = invoke_result_t<F, Arg1, Arg2>;
|
||||||
|
|
||||||
|
static result_type invoke(F &&f, Arg1 &&a, Arg2 &&b) { return detail::invoke(f, a, b); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class Arg1, class Arg2>
|
||||||
|
using invoke_as_binary_fn_result_t = typename binary_fn_helper<F, Arg1, Arg2>::result_type;
|
||||||
|
|
||||||
|
template<class F, class Arg1, class Arg2>
|
||||||
|
invoke_as_binary_fn_result_t<F, Arg1, Arg2>
|
||||||
|
invoke_as_binary_fn(F &&f, Arg1 &&a, Arg2 &&b)
|
||||||
|
{
|
||||||
|
return binary_fn_helper<F, Arg1, Arg2>::invoke(
|
||||||
|
detail::forward<F>(f),
|
||||||
|
detail::forward<Arg1>(a),
|
||||||
|
detail::forward<Arg2>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
// basic template metaprogramming stuff; note that we could
|
||||||
|
// use std::tuple instead of list, but <tuple> may nor be
|
||||||
|
// present on freestanding implementations
|
||||||
|
template<class...>
|
||||||
|
struct list {};
|
||||||
|
|
||||||
|
template<class...>
|
||||||
|
struct concat;
|
||||||
|
|
||||||
|
template<class T, class... Types>
|
||||||
|
struct concat<T, list<Types...>> {
|
||||||
|
using type = list<T, Types...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct concat<> {
|
||||||
|
using type = list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<template<typename> class Predicate, class...>
|
||||||
|
struct filter;
|
||||||
|
|
||||||
|
template<template<typename> class Predicate, class T, class... Types>
|
||||||
|
struct filter<Predicate, T, Types...> {
|
||||||
|
using type = typename std::conditional<Predicate<T>::value,
|
||||||
|
typename concat<T, typename filter<Predicate, Types...>::type>::type,
|
||||||
|
typename filter<Predicate, Types...>::type>::type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<template<typename> class Predicate>
|
||||||
|
struct filter<Predicate> {
|
||||||
|
using type = list<>;
|
||||||
|
};
|
||||||
|
}// namespace detail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finite state machine (FSM) base class template.
|
||||||
|
*
|
||||||
|
* @tparam Derived the derived state machine class
|
||||||
|
*
|
||||||
|
* @tparam State the FSM's state type, defaults to `int`
|
||||||
|
*/
|
||||||
|
template<class Derived, class State = int>
|
||||||
|
class Fsm {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* The FSM's state type.
|
||||||
|
*/
|
||||||
|
typedef State state_type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Create a state machine with an optional initial state.
|
||||||
|
*
|
||||||
|
* @param init_state the FSM's initial state
|
||||||
|
*/
|
||||||
|
Fsm(state_type init_state = state_type()) : m_state(init_state) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an event.
|
||||||
|
*
|
||||||
|
* @warning This member function must not be called
|
||||||
|
* recursively, e.g. from another `fsm` instance.
|
||||||
|
*
|
||||||
|
* @tparam Event the event type
|
||||||
|
*
|
||||||
|
* @param event the event instance
|
||||||
|
*
|
||||||
|
* @throw std::logic_error if a recursive invocation is
|
||||||
|
* detected
|
||||||
|
*/
|
||||||
|
template<class Event>
|
||||||
|
void ProcessEvent(const Event &event)
|
||||||
|
{
|
||||||
|
using rows = typename by_event_type<Event, typename Derived::TransitionTable>::type;
|
||||||
|
processing_lock lock(*this);
|
||||||
|
static_assert(std::is_base_of<Fsm, Derived>::value, "must derive from fsm");
|
||||||
|
Derived &self = static_cast<Derived &>(*this);
|
||||||
|
m_state = handle_event<Event, rows>::execute(self, event, m_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the state machine's current state.
|
||||||
|
*/
|
||||||
|
state_type CurrentState() const { return m_state; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Called when no transition can be found for the given event
|
||||||
|
* in the current state. Derived state machines may override
|
||||||
|
* this to throw an exception, or change to some other (error)
|
||||||
|
* state. The default is to return the current state, so no
|
||||||
|
* state change occurs.
|
||||||
|
*
|
||||||
|
* @tparam Event the event type
|
||||||
|
*
|
||||||
|
* @param event the event instance
|
||||||
|
*
|
||||||
|
* @return the FSM's new state
|
||||||
|
*/
|
||||||
|
template<class Event>
|
||||||
|
state_type NoTransition(const Event & /*event*/)
|
||||||
|
{
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<State start, class Event, State target>
|
||||||
|
struct row_base {
|
||||||
|
using state_type = State;
|
||||||
|
using event_type = Event;
|
||||||
|
|
||||||
|
static constexpr state_type start_value() { return start; }
|
||||||
|
|
||||||
|
static constexpr state_type target_value() { return target; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<class Action>
|
||||||
|
static void ProcessEvent(Action &&action, Derived &self, const Event &event)
|
||||||
|
{
|
||||||
|
detail::invoke_as_binary_fn(action, self, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang++-5.0: constexpr function's return type 'void' is not a literal type
|
||||||
|
static /*constexpr*/ void ProcessEvent(std::nullptr_t, Derived & /*self*/, const Event & /*event*/) {}
|
||||||
|
|
||||||
|
template<class Guard>
|
||||||
|
static bool check_guard(Guard &&guard, const Derived &self, const Event &event)
|
||||||
|
{
|
||||||
|
return detail::invoke_as_binary_fn(guard, self, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr bool check_guard(std::nullptr_t, const Derived &, const Event &) { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Transition table variadic class template.
|
||||||
|
*
|
||||||
|
* Each derived state machine class must define a nested
|
||||||
|
* non-template type `transition_table` that's either derived
|
||||||
|
* from or a type alias of `table`.
|
||||||
|
*/
|
||||||
|
template<class... Rows>
|
||||||
|
using table = detail::list<Rows...>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic transition class template.
|
||||||
|
*
|
||||||
|
* @tparam start the start state of the transition
|
||||||
|
*
|
||||||
|
* @tparam Event the event type triggering the transition
|
||||||
|
*
|
||||||
|
* @tparam target the target state of the transition
|
||||||
|
*
|
||||||
|
* @tparam Action an action function type, or `std::nullptr_t`
|
||||||
|
*
|
||||||
|
* @tparam action a static `Action` instance
|
||||||
|
*
|
||||||
|
* @tparam Guard a guard function type, or `std::nullptr_t`
|
||||||
|
*
|
||||||
|
* @tparam guard a static `Guard` instance
|
||||||
|
*/
|
||||||
|
template<State start,
|
||||||
|
class Event,
|
||||||
|
State target,
|
||||||
|
class Action = std::nullptr_t,
|
||||||
|
Action action = nullptr,
|
||||||
|
class Guard = std::nullptr_t,
|
||||||
|
Guard guard = nullptr>
|
||||||
|
struct basic_row : public row_base<start, Event, target> {
|
||||||
|
static void process_event(Derived &self, const Event &event)
|
||||||
|
{
|
||||||
|
row_base<start, Event, target>::ProcessEvent(action, self, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_guard(const Derived &self, const Event &event)
|
||||||
|
{
|
||||||
|
return row_base<start, Event, target>::check_guard(guard, self, event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if __cplusplus >= 201703L || _MSVC_LANG >= 201703L
|
||||||
|
/**
|
||||||
|
* Generic transition class template (requires C++17).
|
||||||
|
*
|
||||||
|
* @tparam start the start state of the transition
|
||||||
|
*
|
||||||
|
* @tparam Event the event type triggering the transition
|
||||||
|
*
|
||||||
|
* @tparam target the target state of the transition
|
||||||
|
*
|
||||||
|
* @tparam action a static action function pointer, or `nullptr`
|
||||||
|
*
|
||||||
|
* @tparam guard a static guard function pointer, or `nullptr`
|
||||||
|
*/
|
||||||
|
template<State start, class Event, State target, auto action = nullptr, auto guard = nullptr>
|
||||||
|
struct row : public row_base<start, Event, target> {
|
||||||
|
static void ProcessEvent(Derived &self, const Event &event)
|
||||||
|
{
|
||||||
|
row_base<start, Event, target>::process_event(action, self, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_guard(const Derived &self, const Event &event)
|
||||||
|
{
|
||||||
|
return row_base<start, Event, target>::check_guard(guard, self, event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
/**
|
||||||
|
* Member function transition class template.
|
||||||
|
*
|
||||||
|
* @tparam start the start state of the transition
|
||||||
|
*
|
||||||
|
* @tparam Event the event type triggering the transition
|
||||||
|
*
|
||||||
|
* @tparam target the target state of the transition
|
||||||
|
*
|
||||||
|
* @tparam action an action member function, or `nullptr`
|
||||||
|
*
|
||||||
|
* @tparam guard a guard member function, or `nullptr`
|
||||||
|
*/
|
||||||
|
template<State start,
|
||||||
|
class Event,
|
||||||
|
State target,
|
||||||
|
void (Derived::*action)(const Event &) = nullptr,
|
||||||
|
bool (Derived::*guard)(const Event &) const = nullptr>
|
||||||
|
struct row : public row_base<start, Event, target> {
|
||||||
|
static void ProcessEvent(Derived &self, const Event &event)
|
||||||
|
{
|
||||||
|
if (action != nullptr) { row_base<start, Event, target>::ProcessEvent(action, self, event); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_guard(const Derived &self, const Event &event)
|
||||||
|
{
|
||||||
|
if (guard != nullptr) {
|
||||||
|
return row_base<start, Event, target>::check_guard(guard, self, event);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<class Event, class...>
|
||||||
|
struct by_event_type;
|
||||||
|
|
||||||
|
template<class Event, class... Types>
|
||||||
|
struct by_event_type<Event, detail::list<Types...>> {
|
||||||
|
template<class T>
|
||||||
|
using predicate = std::is_same<typename T::event_type, Event>;
|
||||||
|
using type = typename detail::filter<predicate, Types...>::type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Event>
|
||||||
|
struct by_event_type<Event, detail::list<>> {
|
||||||
|
using type = detail::list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Event, class...>
|
||||||
|
struct handle_event;
|
||||||
|
|
||||||
|
template<class Event, class T, class... Types>
|
||||||
|
struct handle_event<Event, detail::list<T, Types...>> {
|
||||||
|
static State execute(Derived &self, const Event &event, State state)
|
||||||
|
{
|
||||||
|
return state == T::start_value() && T::check_guard(self, event) ? T::ProcessEvent(self, event),
|
||||||
|
T::target_value() : handle_event<Event, detail::list<Types...>>::execute(self, event, state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Event>
|
||||||
|
struct handle_event<Event, detail::list<>> {
|
||||||
|
static State execute(Derived &self, const Event &event, State) { return self.NoTransition(event); }
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
state_type m_state;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if !defined(NDEBUG) && (!__GNUC__ || __EXCEPTIONS)
|
||||||
|
class processing_lock {
|
||||||
|
public:
|
||||||
|
processing_lock(Fsm &m) : processing(m.processing)
|
||||||
|
{
|
||||||
|
if (processing) { throw std::logic_error("process_event called recursively"); }
|
||||||
|
processing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
~processing_lock() { processing = false; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool &processing;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool processing = false;
|
||||||
|
#else
|
||||||
|
struct processing_lock {
|
||||||
|
processing_lock(fsm &) {}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
}// namespace fsmlite
|
||||||
|
|
||||||
|
namespace sled {
|
||||||
|
template<typename T, typename State = int>
|
||||||
|
using Fsm = fsmlite::Fsm<T, State>;
|
||||||
|
}
|
||||||
|
#endif
|
76
src/sled/nonstd/fsm_test.cc
Normal file
76
src/sled/nonstd/fsm_test.cc
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#include <sled/nonstd/fsm.h>
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
kIdle,
|
||||||
|
kStarted,
|
||||||
|
kEnd,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FSMTest : public sled::Fsm<FSMTest, State> {
|
||||||
|
public:
|
||||||
|
FSMTest() : Fsm(State::kIdle) {}
|
||||||
|
|
||||||
|
struct StartEvent {
|
||||||
|
StartEvent(bool do_nothing = false) : do_nothing(do_nothing) {}
|
||||||
|
|
||||||
|
bool do_nothing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StopEvent {};
|
||||||
|
|
||||||
|
struct ResetEvent {};
|
||||||
|
|
||||||
|
template<typename E>
|
||||||
|
state_type NoTransition(const E &)
|
||||||
|
{
|
||||||
|
++error_count;
|
||||||
|
return CurrentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
int error_count = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool StartCheck(const StartEvent &e) const { return !e.do_nothing; }
|
||||||
|
|
||||||
|
void OnStart(const StartEvent &) {}
|
||||||
|
|
||||||
|
void OnStop(const StopEvent &) {}
|
||||||
|
|
||||||
|
void OnReset(const ResetEvent &) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
using TransitionTable
|
||||||
|
= table<row<State::kIdle, StartEvent, State::kStarted, &FSMTest::OnStart, &FSMTest::StartCheck>,
|
||||||
|
row<State::kStarted, StopEvent, State::kEnd, &FSMTest::OnStop>,
|
||||||
|
row<State::kEnd, ResetEvent, State::kIdle, &FSMTest::OnReset>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_SUITE("fsm")
|
||||||
|
{
|
||||||
|
TEST_CASE("Entry Exit")
|
||||||
|
{
|
||||||
|
FSMTest fsm_test;
|
||||||
|
CHECK_EQ(fsm_test.CurrentState(), State::kIdle);
|
||||||
|
CHECK_EQ(fsm_test.error_count, 0);
|
||||||
|
|
||||||
|
fsm_test.ProcessEvent(FSMTest::StartEvent(true));
|
||||||
|
CHECK_EQ(fsm_test.CurrentState(), State::kIdle);
|
||||||
|
CHECK_EQ(fsm_test.error_count, 1);
|
||||||
|
|
||||||
|
fsm_test.ProcessEvent(FSMTest::StartEvent());
|
||||||
|
CHECK_EQ(fsm_test.CurrentState(), State::kStarted);
|
||||||
|
CHECK_EQ(fsm_test.error_count, 1);
|
||||||
|
|
||||||
|
fsm_test.ProcessEvent(FSMTest::StopEvent());
|
||||||
|
CHECK_EQ(fsm_test.CurrentState(), State::kEnd);
|
||||||
|
CHECK_EQ(fsm_test.error_count, 1);
|
||||||
|
|
||||||
|
fsm_test.ProcessEvent(FSMTest::ResetEvent());
|
||||||
|
CHECK_EQ(fsm_test.CurrentState(), State::kIdle);
|
||||||
|
CHECK_EQ(fsm_test.error_count, 1);
|
||||||
|
|
||||||
|
fsm_test.ProcessEvent(FSMTest::ResetEvent());
|
||||||
|
CHECK_EQ(fsm_test.CurrentState(), State::kIdle);
|
||||||
|
CHECK_EQ(fsm_test.error_count, 2);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
#include "sled/async/async.h"
|
#include "sled/async/async.h"
|
||||||
#include "sled/nonstd/cxxopts.h"
|
#include "sled/nonstd/cxxopts.h"
|
||||||
#include "sled/nonstd/expected.h"
|
#include "sled/nonstd/expected.h"
|
||||||
|
#include "sled/nonstd/fsm.h"
|
||||||
#include "sled/nonstd/inja.h"
|
#include "sled/nonstd/inja.h"
|
||||||
#include "sled/nonstd/string_view.h"
|
#include "sled/nonstd/string_view.h"
|
||||||
#include "toml.hpp"
|
#include "toml.hpp"
|
||||||
|
Loading…
Reference in New Issue
Block a user