etl/test/test_fsm.cpp
John Wellbelove d3affac417
Enforce o(log n) dispatch for messages when using fsm for c++11 and up (#1337)
* Updated message handling to be worst case O(logN)

* Copied optimised message handling from etl::fsm

* Updated fsm generator

* Updated message_router generator

* Added optimised accepts() member function

* Modified comment, as the FSM doesn't support a successor

* Updated version and release notes

* Hotfix/etl  multiset iterator invalidation during erase leads to incorrect sorted order in depth first traversal (#1317)

* Fixed issue for both multiset and multimap

* Added std::is_sorted checks to all map/set tests

* Updated with coderabbit suggestions

---------

Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com>

* Updated release notes and version

* Changed std::is_same to etl::is_same in struct type_list_is_unique (#1320)

Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com>

* Updated release notes and version

* Fix etl::rotate (#1327)

Per the C++ standard, std::rotate returns first + (last - middle):

* When first == middle, return last
* When middle == last, return first

* Added missing files from VS2022 project

* Fix greater_equal and less_equal (#1331)

* Align comparison operators (#1330)

In functional.h, the comparison operators for equal_to and not_equal_to
mismatch between the actual comparison execution and the type inference
for the return type. This change adjusts it by using the same operator==()
in the return type inference as used in the comparison execution.

Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>

* Add missing tests (#1321)

* Add missing tests

* Typo fixes

---------

Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>

* Add ETL_FORMAT_NO_FLOATING_POINT control macro for etl::format (#1329)

When ETL_FORMAT_NO_FLOATING_POINT is defined, all floating-point formatting support (float, double, long double) is excluded from etl::format. This reduces code size on targets that do not require floating-point formatting.

Guarded sections:

- #include <cmath>

- float/double/long double in supported_format_types variant

- float/double/long double constructors in basic_format_arg

- format_floating_* functions and format_aligned_floating

- formatter<float>, formatter<double>, formatter<long double>

- Floating-point test cases in test_format.cpp

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>

* Manchester documentation (#1325)

* manchester
* Added manchester code and test

* manchester
* Formatting and added missing file

* manchester
* Some functions can only be constexpr since C++14

* manchester
* Manchester decode and some refactoring

* manchester
* Added some missing typenames

* manchester
* constexpr void function not allowed in C++11

* manchester
* condition on static_assert tests

* manchester
* revert CMakeLists.txt
* Using ETL_STATIC_ASSERT
* Some cleanup

* manchester
* Added static_assert message

* manchester
* Added compile time tests

* manchester
* Added invert manchester
* Some refactoring

* manchester
* Disable test for now
* Move ETL_NODISCARD before static

* manchester
* Test for valid_span

* manchester
* Remove redundant (?) storage specifiers for template specializations. Storage specifier already given in base template

* manchester
* refactoring to get rid of specialized template functions in template class

* manchester
* cleanup

* manchester
* Added documentation comments
* Some refactoring

* manchester
* introducing namespace detail_manchester

* manchester
* Some refactoring
* Update tests

* manchester
* Some refactoring
* Removed possible undefined behavior by refactoring encode_span
* constexpr version of encode_span
* Static assertion for rare case where code doesn't work because CHAR_BIT is not the same as the number of bits in uint_least8_t

* manchester
* renamed valid to is_valid

* manchester
* renamed is_valid_span to is_valid
* Using etl exceptions in ETL_ASSERT

* manchester
* Removed _fast functions
* merged encode_in_place with encode and decode_in_place with decode
* removed _span to create normal overloads of encode and decode for span
* Some renaming and minor refactoring

* manchester
* Fix build issues

* manchester
* Conditionally compile manchester_decoded

* Update test_manchester.cpp

Removed redundant semicolon

* #1258 Manchester coding
* Formatting
* consistency: hex literals with lower case 0x

* #1258 Manchester coding
* Moved copyright to top of file
* Make constexpr encode/decode span functions equal for little and big endian platforms

* #1258 Manchester coding
* Added missing include
* Added missing 8bit/64bit guards
* Fixed is_valid for big endian platforms

* #1258 Manchester coding
* private memcpy alias

* #1258 Manchester coding
* Review comments

* #1258 Manchester coding
* Cleanup
* Fix build error

* #1258 Manchester coding
* Add manchester documentation

* #1258 Manchester coding
* Preparation for GitHub pages

* #1324 Manchester documentation
* Some small fixes

---------

Co-authored-by: Timon Zijnge <timon.zijnge@imec.nl>

* Changes from review of algorithm.h on development branch (#1340)

* Add missing constexpr in algorithm.h

* Fix call of nth_element

2nd argument (nth) was missing

* Replace partition point with O(log(N)) algorithm

The C++ standard defines O(log(N)) calls of predicate as the
complexity of partition_point(). The old algorithm was linear.

* Use predicate in calculation of is_permutation consistently

In case of predicate not equal_to, the calculation previously
returned wron results

* Omit swap in selection_sort if iterators are equal

* Use difference_type in rotate_general() instead of int

* Typo fix in algorithm.h

* Simplifications in algorithm.h

Application of plain refactoring by keeping semantics

* Guard against past-end iterator in etl::rotate()

And fix scope of rotate_right_by_one for etl::rotate()

* Support empty ranges in selection_sort

* Add tests for swap_ranges

* Add tests for binary_search

* Add tests for find_end

* Add tests for accumulate

* Add tests for move_s

* Added tests for is_heap and sort_heap

* Remove early exit for empty input

* Add adjacent_find

* Add unique

* Add unique_copy

* Add merge

* Add inplace_merge

* Add partial_sort

* Add partial_sort_copy

* copilot review change

---------

Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com>
Co-authored-by: Roland Reichwein <Roland.Reichwein@bmw.de>
Co-authored-by: Niu Zhihong <zhihong@nzhnb.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Timon Zijnge <47081647+tzijnge@users.noreply.github.com>
Co-authored-by: Timon Zijnge <timon.zijnge@imec.nl>
2026-03-12 17:06:26 +00:00

785 lines
23 KiB
C++

/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2017 John Wellbelove
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.
******************************************************************************/
#include "unit_test_framework.h"
#include "etl/fsm.h"
#include "etl/enum_type.h"
#include "etl/container.h"
#include "etl/packet.h"
#include "etl/queue.h"
#include <iostream>
#include <limits>
namespace
{
const etl::message_router_id_t Motor_Control = 0;
//***************************************************************************
// Events
struct EventId
{
enum enum_type
{
Start,
Stop,
Stopped,
Set_Speed,
Recursive,
Self_Transition,
Unsupported
};
ETL_DECLARE_ENUM_TYPE(EventId, etl::message_id_t)
ETL_ENUM_TYPE(Start, "Start")
ETL_ENUM_TYPE(Stop, "Stop")
ETL_ENUM_TYPE(Stopped, "Stopped")
ETL_ENUM_TYPE(Set_Speed, "Set Speed")
ETL_ENUM_TYPE(Recursive, "Recursive")
ETL_ENUM_TYPE(Self_Transition, "Self Transition")
ETL_ENUM_TYPE(Unsupported, "Unsupported")
ETL_END_ENUM_TYPE
};
//***********************************
class Start : public etl::message<EventId::Start>
{
};
//***********************************
class Stop : public etl::message<EventId::Stop>
{
public:
Stop() : isEmergencyStop(false) {}
Stop(bool emergency) : isEmergencyStop(emergency) {}
const bool isEmergencyStop;
};
//***********************************
class SetSpeed : public etl::message<EventId::Set_Speed>
{
public:
SetSpeed(int speed_) : speed(speed_) {}
const int speed;
};
//***********************************
class Stopped : public etl::message<EventId::Stopped>
{
};
//***********************************
class Recursive : public etl::message<EventId::Recursive>
{
};
//***********************************
class SelfTransition : public etl::message<EventId::Self_Transition>
{
};
//***********************************
class Unsupported : public etl::message<EventId::Unsupported>
{
};
//***************************************************************************
// States
struct StateId
{
enum enum_type
{
Idle,
Running,
Winding_Down,
Locked,
Number_Of_States
};
ETL_DECLARE_ENUM_TYPE(StateId, etl::fsm_state_id_t)
ETL_ENUM_TYPE(Idle, "Idle")
ETL_ENUM_TYPE(Running, "Running")
ETL_ENUM_TYPE(Winding_Down, "Winding Down")
ETL_ENUM_TYPE(Locked, "Locked")
ETL_END_ENUM_TYPE
};
//***********************************
// The motor control FSM.
//***********************************
class MotorControl : public etl::fsm
{
public:
MotorControl()
: fsm(Motor_Control)
{
}
//***********************************
void Initialise(etl::ifsm_state** p_states, size_t size)
{
set_states(p_states, size);
ClearStatistics();
}
//***********************************
template <typename... TStates>
void Initialise(etl::fsm_state_pack<TStates...>& state_pack)
{
set_states(state_pack);
ClearStatistics();
}
//***********************************
void ClearStatistics()
{
startCount = 0;
stopCount = 0;
setSpeedCount = 0;
unknownCount = 0;
stoppedCount = 0;
exited_state = false;
entered_state = false;
isLampOn = false;
speed = 0;
last_event_id = std::numeric_limits<etl::message_id_t>::max();
last_returned_state_id = std::numeric_limits<etl::fsm_state_id_t>::max();
}
//***********************************
void SetSpeedValue(int speed_)
{
speed = speed_;
}
//***********************************
void TurnRunningLampOn()
{
isLampOn = true;
}
//***********************************
void TurnRunningLampOff()
{
isLampOn = false;
}
//***********************************
template <typename T>
void queue_recursive_message(const T& message)
{
messageQueue.emplace(message);
}
typedef etl::largest<Start, Stop, SetSpeed, Stopped, Recursive, SelfTransition> Largest_t;
typedef etl::packet<etl::imessage, Largest_t::size, Largest_t::alignment> Packet_t;
etl::queue<Packet_t, 2> messageQueue;
int startCount;
int stopCount;
int setSpeedCount;
int unknownCount;
int stoppedCount;
bool exited_state;
bool entered_state;
bool isLampOn;
int speed;
etl::message_id_t last_event_id;
etl::fsm_state_id_t last_returned_state_id;
};
//***********************************
// The idle state.
//***********************************
class Idle : public etl::fsm_state<MotorControl, Idle, StateId::Idle, Start, Recursive, SelfTransition>
{
public:
//***********************************
etl::fsm_state_id_t on_event(const Start&)
{
++get_fsm_context().startCount;
return StateId::Running;
}
//***********************************
etl::fsm_state_id_t on_event(const Recursive&)
{
get_fsm_context().queue_recursive_message(Start());
return StateId::Idle;
}
//***********************************
etl::fsm_state_id_t on_event(const SelfTransition&)
{
get_fsm_context().last_event_id = SelfTransition::ID;
get_fsm_context().last_returned_state_id = etl::ifsm_state::Self_Transition;
return etl::ifsm_state::Self_Transition;
}
//***********************************
etl::fsm_state_id_t on_event_unknown(const etl::imessage&)
{
++get_fsm_context().unknownCount;
return StateId::Idle; //No_State_Change;
}
//***********************************
etl::fsm_state_id_t on_enter_state()
{
get_fsm_context().TurnRunningLampOff();
get_fsm_context().entered_state = true;
return StateId::Locked;
}
//***********************************
void on_exit_state()
{
get_fsm_context().exited_state = true;
}
};
//***********************************
// The running state.
//***********************************
class Running : public etl::fsm_state<MotorControl, Running, StateId::Running, Stop, SetSpeed>
{
public:
//***********************************
etl::fsm_state_id_t on_event(const Stop& event)
{
++get_fsm_context().stopCount;
if (event.isEmergencyStop)
{
return StateId::Idle;
}
else
{
return StateId::Winding_Down;
}
}
//***********************************
etl::fsm_state_id_t on_event(const SetSpeed& event)
{
++get_fsm_context().setSpeedCount;
get_fsm_context().SetSpeedValue(event.speed);
return No_State_Change;
}
//***********************************
etl::fsm_state_id_t on_event_unknown(const etl::imessage&)
{
++get_fsm_context().unknownCount;
return No_State_Change;
}
//***********************************
etl::fsm_state_id_t on_enter_state()
{
get_fsm_context().TurnRunningLampOn();
return No_State_Change;
}
};
//***********************************
// The winding down state.
//***********************************
class WindingDown : public etl::fsm_state<MotorControl, WindingDown, StateId::Winding_Down, Stopped>
{
public:
//***********************************
etl::fsm_state_id_t on_event(const Stopped&)
{
++get_fsm_context().stoppedCount;
return StateId::Idle;
}
//***********************************
etl::fsm_state_id_t on_event_unknown(const etl::imessage&)
{
++get_fsm_context().unknownCount;
return No_State_Change;
}
};
//***********************************
// The locked state.
//***********************************
class Locked : public etl::fsm_state<MotorControl, Locked, StateId::Locked>
{
public:
//***********************************
etl::fsm_state_id_t on_event_unknown(const etl::imessage&)
{
++get_fsm_context().unknownCount;
return No_State_Change;
}
};
// The states.
Idle idle;
Running running;
WindingDown windingDown;
Locked locked;
etl::ifsm_state* stateList[StateId::Number_Of_States] =
{
&idle, &running, &windingDown, &locked
};
MotorControl motorControl;
SUITE(test_fsm_states)
{
//*************************************************************************
TEST(test_fsm)
{
CHECK(motorControl.is_producer());
CHECK(motorControl.is_consumer());
motorControl.Initialise(stateList, ETL_OR_STD17::size(stateList));
motorControl.reset();
motorControl.ClearStatistics();
CHECK(!motorControl.is_started());
// Start the FSM.
motorControl.start(false);
CHECK(motorControl.is_started());
// Now in Idle state.
CHECK_EQUAL(StateId::Idle, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Idle, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(false, motorControl.isLampOn);
CHECK_EQUAL(0, motorControl.setSpeedCount);
CHECK_EQUAL(0, motorControl.speed);
CHECK_EQUAL(0, motorControl.startCount);
CHECK_EQUAL(0, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(0, motorControl.unknownCount);
// Send unhandled events.
motorControl.receive(Stop());
motorControl.receive(Stopped());
motorControl.receive(SetSpeed(10));
CHECK_EQUAL(StateId::Idle, motorControl.get_state_id());
CHECK_EQUAL(StateId::Idle, motorControl.get_state().get_state_id());
CHECK_EQUAL(false, motorControl.isLampOn);
CHECK_EQUAL(0, motorControl.setSpeedCount);
CHECK_EQUAL(0, motorControl.speed);
CHECK_EQUAL(0, motorControl.startCount);
CHECK_EQUAL(0, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(3, motorControl.unknownCount);
// Send Start event.
motorControl.receive(Start());
// Now in Running state.
CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(true, motorControl.isLampOn);
CHECK_EQUAL(0, motorControl.setSpeedCount);
CHECK_EQUAL(0, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(0, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(3, motorControl.unknownCount);
// Send unhandled events.
motorControl.receive(Start());
motorControl.receive(Stopped());
CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(true, motorControl.isLampOn);
CHECK_EQUAL(0, motorControl.setSpeedCount);
CHECK_EQUAL(0, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(0, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(5, motorControl.unknownCount);
// Send SetSpeed event.
motorControl.receive(SetSpeed(100));
// Still in Running state.
CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(true, motorControl.isLampOn);
CHECK_EQUAL(1, motorControl.setSpeedCount);
CHECK_EQUAL(100, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(0, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(5, motorControl.unknownCount);
// Send Stop event.
motorControl.receive(Stop());
// Now in WindingDown state.
CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(true, motorControl.isLampOn);
CHECK_EQUAL(1, motorControl.setSpeedCount);
CHECK_EQUAL(100, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(1, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(5, motorControl.unknownCount);
// Send unhandled events.
motorControl.receive(Start());
motorControl.receive(Stop());
motorControl.receive(SetSpeed(100));
CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(true, motorControl.isLampOn);
CHECK_EQUAL(1, motorControl.setSpeedCount);
CHECK_EQUAL(100, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(1, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(8, motorControl.unknownCount);
// Send Stopped event.
motorControl.receive(Stopped());
// Now in Locked state via Idle state.
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(false, motorControl.isLampOn);
CHECK_EQUAL(1, motorControl.setSpeedCount);
CHECK_EQUAL(100, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(1, motorControl.stopCount);
CHECK_EQUAL(1, motorControl.stoppedCount);
CHECK_EQUAL(8, motorControl.unknownCount);
}
//*************************************************************************
TEST(test_fsm_emergency_stop)
{
motorControl.Initialise(stateList, ETL_OR_STD17::size(stateList));
motorControl.reset();
motorControl.ClearStatistics();
CHECK(!motorControl.is_started());
// Start the FSM.
motorControl.start(false);
CHECK(motorControl.is_started());
// Now in Idle state.
// Send Start event.
motorControl.receive(Start());
// Now in Running state.
CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(true, motorControl.isLampOn);
CHECK_EQUAL(0, motorControl.setSpeedCount);
CHECK_EQUAL(0, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(0, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(0, motorControl.unknownCount);
// Send emergency Stop event.
motorControl.receive(Stop(true));
// Now in Locked state via Idle state.
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(false, motorControl.isLampOn);
CHECK_EQUAL(0, motorControl.setSpeedCount);
CHECK_EQUAL(0, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(1, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(0, motorControl.unknownCount);
}
//*************************************************************************
TEST(test_fsm_recursive_event)
{
motorControl.Initialise(stateList, ETL_OR_STD17::size(stateList));
motorControl.reset();
motorControl.ClearStatistics();
motorControl.messageQueue.clear();
// Start the FSM.
motorControl.start(false);
// Now in Idle state.
// Send Start event.
motorControl.receive(Recursive());
CHECK_EQUAL(1U, motorControl.messageQueue.size());
// Send the queued message.
motorControl.receive(motorControl.messageQueue.front().get());
motorControl.messageQueue.pop();
// Now in Running state.
CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id()));
CHECK_EQUAL(true, motorControl.isLampOn);
CHECK_EQUAL(0, motorControl.setSpeedCount);
CHECK_EQUAL(0, motorControl.speed);
CHECK_EQUAL(1, motorControl.startCount);
CHECK_EQUAL(0, motorControl.stopCount);
CHECK_EQUAL(0, motorControl.stoppedCount);
CHECK_EQUAL(0, motorControl.unknownCount);
}
//*************************************************************************
TEST(test_fsm_supported)
{
CHECK(motorControl.accepts(EventId::Set_Speed));
CHECK(motorControl.accepts(EventId::Start));
CHECK(motorControl.accepts(EventId::Stop));
CHECK(motorControl.accepts(EventId::Stopped));
CHECK(motorControl.accepts(EventId::Unsupported));
CHECK(motorControl.accepts(SetSpeed(0)));
CHECK(motorControl.accepts(Start()));
CHECK(motorControl.accepts(Stop()));
CHECK(motorControl.accepts(Stopped()));
CHECK(motorControl.accepts(Unsupported()));
}
//*************************************************************************
TEST(test_fsm_no_states)
{
MotorControl mc;
// No states.
etl::ifsm_state** stateList = nullptr;
CHECK_THROW(mc.set_states(stateList, 0U), etl::fsm_state_list_exception);
}
//*************************************************************************
TEST(test_fsm_null_state)
{
MotorControl mc;
// Null state.
etl::ifsm_state* stateList[StateId::Number_Of_States] =
{
&idle, &running,& windingDown, nullptr
};
CHECK_THROW(mc.set_states(stateList, StateId::Number_Of_States), etl::fsm_null_state_exception);
}
//*************************************************************************
TEST(test_fsm_incorrect_state_order)
{
MotorControl mc;
// Incorrect order.
etl::ifsm_state* stateList[StateId::Number_Of_States] =
{
&idle, &windingDown, &running, &locked
};
CHECK_THROW(mc.set_states(stateList, StateId::Number_Of_States), etl::fsm_state_list_order_exception);
}
//*************************************************************************
TEST(test_fsm_self_transition)
{
MotorControl motorControl;
motorControl.Initialise(stateList, ETL_OR_STD17::size(stateList));
motorControl.reset();
motorControl.ClearStatistics();
// Start the FSM.
motorControl.start(false);
CHECK_FALSE(motorControl.exited_state);
CHECK_FALSE(motorControl.entered_state);
// Execute self transition.
motorControl.receive(SelfTransition());
CHECK_EQUAL(size_t(SelfTransition::ID), size_t(motorControl.last_event_id));
CHECK_EQUAL(size_t(etl::ifsm_state::Self_Transition), size_t(motorControl.last_returned_state_id));
CHECK_TRUE(motorControl.exited_state);
CHECK_TRUE(motorControl.entered_state);
}
//*************************************************************************
TEST(test_fsm_no_states_and_no_start)
{
MotorControl mc;
CHECK_THROW(mc.receive(Start()), etl::fsm_not_started);
}
//*************************************************************************
TEST(test_fsm_initialise_from_state_pack)
{
MotorControl motorControl;
etl::fsm_state_pack<Idle, Running, WindingDown, Locked> statePack;
motorControl.Initialise(statePack);
motorControl.reset();
motorControl.ClearStatistics();
// Start the FSM.
motorControl.start(false);
CHECK(motorControl.is_started());
// Now in Idle state.
CHECK_EQUAL(StateId::Idle, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Idle, int(motorControl.get_state().get_state_id()));
// Send Start event.
motorControl.receive(Start());
// Now in Running state.
CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id()));
// Send SetSpeed event.
motorControl.receive(SetSpeed(100));
// Still in Running state.
CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id()));
// Send Stop event.
motorControl.receive(Stop());
// Now in WindingDown state.
CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state().get_state_id()));
// Send Stopped event.
motorControl.receive(Stopped());
// Now in Locked state via Idle state.
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id()));
}
//*************************************************************************
TEST(test_fsm_force_state_changes)
{
MotorControl motorControl;
etl::fsm_state_pack<Idle, Running, WindingDown, Locked> statePack;
motorControl.Initialise(statePack);
motorControl.reset();
motorControl.ClearStatistics();
// Start the FSM.
motorControl.start(false);
CHECK(motorControl.is_started());
// Now in Idle state.
CHECK_EQUAL(StateId::Idle, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Idle, int(motorControl.get_state().get_state_id()));
auto id1 = motorControl.transition_to(StateId::Running);
// Now in Running state.
CHECK_EQUAL(StateId::Running, int(id1));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id()));
auto id2 = motorControl.transition_to(StateId::Idle);
// Now in Locked state.
CHECK_EQUAL(StateId::Locked, int(id2));
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id()));
auto id3 = motorControl.transition_to(StateId::Winding_Down);
// Now in Locked state.
CHECK_EQUAL(StateId::Winding_Down, int(id3));
CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state().get_state_id()));
// Send a normal event message to make sure the FSM is still working.
// Now send a Stopped event message.
motorControl.receive(Stopped());
// Now in Idle state.
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id()));
CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id()));
}
}
}