Skip to content
Snippets Groups Projects
Select Git revision
  • sbs-args-rework
  • twelve-param-correction
  • nas-pitot-correction
  • main default protected
  • nd015x-dma
  • logger-documentation
  • arp
  • arp-gyro
  • async-fsm
  • chipselect-mux
  • nas-catch-dev
  • parafoil-mavlink-upd
  • mockup-main-software
  • quadspi-flash
  • quadspi-flash2
  • sx1278-resilience
  • units-impl
  • nokia-tm-dev
  • spi
  • cc3135
  • ARP-pre-2.7
  • PYXIS_ROCCARASO
  • PYXIS_EUROC
  • lynx-euroc
  • hermes-v1.0
  • hermes-flight-1
26 results

example-test-fsm.cpp

Blame
  • example-test-fsm.cpp 6.63 KiB
    /* Copyright (c) 2019 Skyward Experimental Rocketry
     * Author: Luca Erbetta
     *
     * 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.
     */
    
    // Define STANDALONE_CATCH1_TEST in sbs.conf if you want to run this test alone
    // Otherwise, include this file in the sources and compile the
    // catch1-tests-entry.cpp entrypoint. This test will be run automatically
    // togheter with all the others. Learn more on the skyward-boardcore wiki at:
    // https://git.skywarder.eu/r2a/skyward-boardcore/wikis/Testing
    #ifdef STANDALONE_CATCH1_TEST
    #include "catch-tests-entry.cpp"
    #endif
    
    // We need access to the handleEvent(...) function in state machines in order to
    // test them synchronously
    #define protected public
    
    #include <miosix.h>
    #include <utils/TestUtils/TestHelper.h>
    
    #include <catch2/catch.hpp>
    
    #include "example-test-fsm.h"
    
    using miosix::Thread;
    
    TEST_CASE("Testing S1 transitions")
    {
        FSMExample fsm;
    
        SECTION("S1 -> EV_A -> S2")
        {
            REQUIRE(testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S2));
        }
    
        SECTION("S1 -> EV_A -> S4 if we go to S3 before")
        {
            // Go to S3 and then back to S1
            REQUIRE(testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S2));
            REQUIRE(testFSMTransition(fsm, Event{EV_C}, &FSMExample::state_S3));
            REQUIRE(testFSMTransition(fsm, Event{EV_D}, &FSMExample::state_S1));
    
            // Test if EV_A now puts us in S4
            REQUIRE(testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S4));
        }
    }
    
    TEST_CASE("Testing S2 Transitions")
    {
        // These lines are repeated every time we enter a "SECTION". This means that
        // the state machine will be in state S2 at the beginning of every section.
        FSMExample fsm;
        // Move to S2 before testing
        REQUIRE(testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S2));
    
        // Test S2 to S3
        SECTION("S2 -> EV_C -> S3")
        {
            REQUIRE(testFSMTransition(fsm, Event{EV_C}, &FSMExample::state_S3));
        }
    }
    
    TEST_CASE("Testing S3 transitions")
    {
        // First move to S3
        FSMExample fsm;
        REQUIRE(testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S2));
        REQUIRE(testFSMTransition(fsm, Event{EV_C}, &FSMExample::state_S3));
    
        // Then test S3 transitions
    
        SECTION("S3 -> EV_D -> S1")
        {
            REQUIRE(testFSMTransition(fsm, Event{EV_D}, &FSMExample::state_S1));
        }
    
        SECTION("S3 -> EV_A -> S3")
        {
            REQUIRE(testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S3));
        }
    }
    
    TEST_CASE("Testing S4 transitions")
    {
        // First move to S4
        FSMExample fsm;
        REQUIRE(testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S2));
        REQUIRE(testFSMTransition(fsm, Event{EV_C}, &FSMExample::state_S3));
        REQUIRE(testFSMTransition(fsm, Event{EV_D}, &FSMExample::state_S1));
        REQUIRE(testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S4));
    
        // Then test S4 transitions
    
        SECTION("S4 -> EV_D -> S3")
        {
            REQUIRE(testFSMTransition(fsm, Event{EV_D}, &FSMExample::state_S3));
        }
    
        SECTION("S4 -> EV_A -> S1 (Should not happen)")
        {
            REQUIRE_FALSE(
                testFSMTransition(fsm, Event{EV_A}, &FSMExample::state_S1));
        }
    }
    
    /**
     * @brief Test fixture to perform creation / termination of the FSMExample
     * thread, RAII style
     */
    class FSMTestFixture
    {
    public:
        // Start the thread in the constructor
        FSMTestFixture()
        {
            EventBroker::getInstance().start();
            fsm.start();
        }
        // Stop the thread in the destructor
        ~FSMTestFixture() { fsm.stop(); }
    
    protected:
        FSMExample fsm;
    };
    
    // Incredibly, this method is part of a class derived from the "FSMTestFixture"
    // class. So it can access all its protected members. For each  section in this
    // method, the class is created and destroyed, so all sections start exactly at
    // the same state.
    TEST_CASE_METHOD(FSMTestFixture, "Testing async transitions")
    {
        REQUIRE(testFSMAsyncTransition(fsm, Event{EV_A}, TOPIC_T1,
                                       &FSMExample::state_S2));
    
        REQUIRE(testFSMAsyncTransition(fsm, Event{EV_C}, TOPIC_T1,
                                       &FSMExample::state_S3));
        SECTION("Automatic transition")
        {
            SECTION("S3 --> S1 after 1 seconds")
            {
                // Should automatically move to S1 after 1 second.
                Thread::sleep(995);
                // Transition should not have occured yet
                REQUIRE(fsm.testState(&FSMExample::state_S3));
    
                Thread::sleep(10);
                // Now it should
                REQUIRE(fsm.testState(&FSMExample::state_S1));
            }
    
            SECTION("S3 --> S4 if EV_B. Delayed event should be removed")
            {
                EventCounter counter{*EventBroker::getInstance()};
    
                // Post EV_B before EV_D fires. should move to S4 and remove EV_D
                // from the delayed events
                REQUIRE(testFSMAsyncTransition(fsm, Event{EV_B}, TOPIC_T1,
                                               &FSMExample::state_S4));
    
                // Subscribe the counter to TOPIC_1 to see if EV_D still fires
                counter.subscribe(TOPIC_T1);
    
                // Wait for EV_D to fire
                Thread::sleep(1005);
    
                // Should not have received EV_D, since S3 should have removed the
                // delayed event on exit. Spoiler: S3 didn't remove it, and the
                // check will fail (CHECK works just like REQUIRE, but it will not
                // stop the execution of the test in case it fails)
                CHECK(counter.getCount(EV_D) == 0);
    
                // Since S3 did not remove the delyaed event on exit, the event is
                // fired when we are in S4, causing an unexpected transition back to
                // S3, failing this test
                REQUIRE(fsm.testState(&FSMExample::state_S4));
            }
        }
    }