Fix unit tests

- circular buffer can return its count of elements (even though a better solution may be implemeted later)
- stub_motion can handle multiple planned moves
- improved load/unload filament tests
pull/165/head
D.R.racer 2022-05-04 09:07:03 +02:00 committed by DRracer
parent 1d8c1e8f3f
commit 8ce029a28c
13 changed files with 110 additions and 55 deletions

View File

@ -56,8 +56,15 @@ public:
}
/// @returns number of elements in the buffer
/// @@TODO better solution if it exists
inline index_t count() const {
return 0; // @@TODO
index_t i = tail;
index_t c = 0;
while (i != head) {
i = next(i);
++c;
}
return c;
}
protected:
@ -122,6 +129,10 @@ public:
return true;
}
index_t count() const {
return index.count();
}
protected:
T data[size]; ///< array of stored elements
CircularIndex<index_t, size> index; ///< circular index

View File

@ -21,7 +21,7 @@ void LoadFilament::Reset(uint8_t param) {
}
dbg_logic_P(PSTR("Load Filament"));
mg::globals.SetFilamentLoaded(param, mg::FilamentLoadState::AtPulley); // still at pulley, haven't moved yet
Reset2(false);
Reset2(true);
}
void LoadFilament::ResetUnlimited(uint8_t param) {
@ -30,13 +30,13 @@ void LoadFilament::ResetUnlimited(uint8_t param) {
}
dbg_logic_P(PSTR("Load Filament"));
mg::globals.SetFilamentLoaded(param, mg::FilamentLoadState::AtPulley); // still at pulley, haven't moved yet
Reset2(true);
Reset2(false);
}
void logic::LoadFilament::Reset2(bool unlimited) {
void logic::LoadFilament::Reset2(bool feedPhaseLimited) {
state = ProgressCode::FeedingToFinda;
error = ErrorCode::RUNNING;
feed.Reset(unlimited, true);
feed.Reset(feedPhaseLimited, true);
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::blink0, ml::off);
}
@ -100,7 +100,7 @@ bool LoadFilament::StepInner() {
case mui::Event::Middle: // try again the whole sequence
// however it depends on the state of FINDA - if it is on, we must perform unload first
if (!mf::finda.Pressed()) {
Reset2();
Reset2(false);
} else {
GoToRetractingFromFinda();
}

View File

@ -26,7 +26,7 @@ public:
private:
void GoToRetractingFromFinda();
void Reset2(bool unlimited);
void Reset2(bool feedPhaseLimited);
/// Common code for a correct completion of UnloadFilament
void FinishedCorrectly();

View File

@ -313,11 +313,15 @@ public:
bool QueueEmpty(Axis axis) const;
#endif
#if !defined(UNITTEST) || defined(UNITTEST_MOTION)
/// @returns number of planned moves on an axis
uint8_t PlannedMoves(Axis axis) const {
return axisData[axis].ctrl.QueueEmpty();
return axisData[axis].ctrl.PlannedMoves();
}
#else
// Force STUB for testing
uint8_t PlannedMoves(Axis axis) const;
#endif
/// @returns false if new moves can still be planned for one axis
/// @param axis axis requested
bool Full(Axis axis) const { return axisData[axis].ctrl.Full(); }

View File

@ -14,6 +14,7 @@ TEST_CASE("circular_buffer::basic", "[circular_buffer]") {
// since its capacity was defined as 32, at least one element must be successfully inserted
CHECK(cb.push(1));
CHECK(cb.count() == 1);
// is the element there?
REQUIRE(!cb.empty());
@ -24,6 +25,7 @@ TEST_CASE("circular_buffer::basic", "[circular_buffer]") {
CHECK(cb.pop(b));
CHECK(b == 1);
CHECK(cb.empty());
CHECK(cb.count() == 0);
}
TEST_CASE("circular_buffer::fill", "[circular_buffer]") {
@ -34,24 +36,30 @@ TEST_CASE("circular_buffer::fill", "[circular_buffer]") {
// start with an empty buffer
CB cb;
REQUIRE(cb.empty());
REQUIRE(cb.count() == 0);
// ensure we can fill the buffer
for (auto i = 0; i != size; ++i) {
CHECK(!cb.full());
cb.push(i);
CHECK(cb.count() == i + 1);
}
REQUIRE(cb.full());
REQUIRE(cb.count() == size);
// ensure another push fails
REQUIRE(!cb.push(0));
REQUIRE(cb.count() == size);
// retrieve all elements
for (auto i = 0; i != size; ++i) {
uint8_t v;
CHECK(cb.pop(v));
CHECK(v == i);
REQUIRE(cb.count() == size - i - 1);
}
REQUIRE(cb.empty());
REQUIRE(cb.count() == 0);
}
TEST_CASE("circular_buffer::wrap_around", "[circular_buffer]") {
@ -84,15 +92,18 @@ TEST_CASE("circular_buffer::wrap_around", "[circular_buffer]") {
CHECK(!cb.full());
cb.push(i);
CHECK(!cb.empty());
CHECK(cb.count() == i + 1);
}
REQUIRE(cb.full());
REQUIRE(!cb.empty());
REQUIRE(cb.count() == size);
// retrieve all elements
for (auto i = 0; i != size; ++i) {
uint8_t v;
CHECK(cb.pop(v));
CHECK(v == i);
CHECK(cb.count() == size - i - 1);
}
REQUIRE(cb.empty());
}

View File

@ -36,8 +36,8 @@ TEST_CASE("eject_filament::eject0", "[eject_filament][.]") {
// it should have instructed the selector and idler to move to slot 1
// check if the idler and selector have the right command
CHECK(mm::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0).v);
CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(4).v);
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(4).v);
// now cycle at most some number of cycles (to be determined yet) and then verify, that the idler and selector reached their target positions
REQUIRE(WhileTopState(ef, ProgressCode::SelectingFilamentSlot, 5000));

View File

@ -37,8 +37,8 @@ TEST_CASE("feed_to_bondtech::feed_phase_unlimited", "[feed_to_bondtech]") {
// it should have instructed the selector and idler to move to slot 0
// check if the idler and selector have the right command
CHECK(mm::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0).v);
CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
CHECK(mm::axes[mm::Idler].enabled == true);
// engaging idler

View File

@ -37,8 +37,8 @@ TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
// it should have instructed the selector and idler to move to slot 1
// check if the idler and selector have the right command
CHECK(mm::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0).v);
CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
// engaging idler
REQUIRE(WhileCondition(
@ -51,7 +51,7 @@ TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
CHECK(mm::axes[mm::Pulley].enabled == true);
// idler engaged, selector in position, we'll start pushing filament
REQUIRE(ff.State() == FeedToFinda::PushingFilament);
REQUIRE(ff.State() == FeedToFinda::PushingFilamentUnlimited);
// at least at the beginning the LED should shine green (it should be blinking, but this mode has been already verified in the LED's unit test)
REQUIRE(ml::leds.Mode(mg::globals.ActiveSlot(), ml::green) == ml::blink0);
@ -60,7 +60,7 @@ TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
REQUIRE(WhileCondition(
ff,
[&](uint32_t) { return ff.State() == FeedToFinda::PushingFilament; },
[&](uint32_t) { return ff.State() == FeedToFinda::PushingFilamentUnlimited; },
1500));
// From now on the FINDA is reported as ON
@ -105,8 +105,8 @@ TEST_CASE("feed_to_finda::FINDA_failed", "[feed_to_finda]") {
// it should have instructed the selector and idler to move to slot 1
// check if the idler and selector have the right command
CHECK(mm::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0).v);
CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
// engaging idler
REQUIRE(WhileCondition(

View File

@ -19,10 +19,11 @@
#include "../stubs/stub_motion.h"
using Catch::Matchers::Equals;
using namespace std::placeholders;
#include "../helpers/helpers.ipp"
void LoadFilamentCommonSetup(uint8_t slot, logic::LoadFilament &lf) {
void LoadFilamentCommonSetup(uint8_t slot, logic::LoadFilament &lf, bool feedUnlimited) {
ForceReinitAllAutomata();
// change the startup to what we need here
@ -32,7 +33,11 @@ void LoadFilamentCommonSetup(uint8_t slot, logic::LoadFilament &lf) {
REQUIRE(VerifyState(lf, mg::FilamentLoadState::AtPulley, mi::Idler::IdleSlotIndex(), slot, false, false, ml::off, ml::off, ErrorCode::OK, ProgressCode::OK));
// restart the automaton
lf.Reset(slot);
if (feedUnlimited) {
lf.ResetUnlimited(slot);
} else {
lf.Reset(slot);
}
// Stage 0 - verify state just after Reset()
// we assume the filament is not loaded
@ -76,7 +81,7 @@ void LoadFilamentSuccessful(uint8_t slot, logic::LoadFilament &lf) {
TEST_CASE("load_filament::regular_load_to_slot_0-4", "[load_filament]") {
for (uint8_t slot = 0; slot < config::toolCount; ++slot) {
logic::LoadFilament lf;
LoadFilamentCommonSetup(slot, lf);
LoadFilamentCommonSetup(slot, lf, false);
LoadFilamentSuccessful(slot, lf);
}
}
@ -207,7 +212,7 @@ void FailedLoadToFindaResolveTryAgain(uint8_t slot, logic::LoadFilament &lf) {
TEST_CASE("load_filament::failed_load_to_finda_0-4_resolve_help_second_ok", "[load_filament]") {
for (uint8_t slot = 0; slot < config::toolCount; ++slot) {
logic::LoadFilament lf;
LoadFilamentCommonSetup(slot, lf);
LoadFilamentCommonSetup(slot, lf, false);
FailedLoadToFinda(slot, lf);
FailedLoadToFindaResolveHelp(slot, lf);
FailedLoadToFindaResolveHelpFindaTriggered(slot, lf);
@ -217,7 +222,7 @@ TEST_CASE("load_filament::failed_load_to_finda_0-4_resolve_help_second_ok", "[lo
TEST_CASE("load_filament::failed_load_to_finda_0-4_resolve_help_second_fail", "[load_filament]") {
for (uint8_t slot = 0; slot < config::toolCount; ++slot) {
logic::LoadFilament lf;
LoadFilamentCommonSetup(slot, lf);
LoadFilamentCommonSetup(slot, lf, false);
FailedLoadToFinda(slot, lf);
FailedLoadToFindaResolveHelp(slot, lf);
FailedLoadToFindaResolveHelpFindaDidntTrigger(slot, lf);
@ -240,7 +245,7 @@ TEST_CASE("load_filament::state_machine_reusal", "[load_filament]") {
if (toSlot >= config::toolCount) {
InvalidSlot<logic::LoadFilament>(lf, fromSlot, toSlot);
} else {
LoadFilamentCommonSetup(toSlot, lf);
LoadFilamentCommonSetup(toSlot, lf, false);
LoadFilamentSuccessful(toSlot, lf);
}
}
@ -250,7 +255,7 @@ TEST_CASE("load_filament::state_machine_reusal", "[load_filament]") {
TEST_CASE("load_filament::failed_load_to_finda_0-4_resolve_manual", "[load_filament]") {
for (uint8_t slot = 0; slot < config::toolCount; ++slot) {
logic::LoadFilament lf;
LoadFilamentCommonSetup(slot, lf);
LoadFilamentCommonSetup(slot, lf, false);
FailedLoadToFinda(slot, lf);
FailedLoadToFindaResolveManual(slot, lf);
}
@ -259,7 +264,7 @@ TEST_CASE("load_filament::failed_load_to_finda_0-4_resolve_manual", "[load_filam
TEST_CASE("load_filament::failed_load_to_finda_0-4_resolve_manual_no_FINDA", "[load_filament]") {
for (uint8_t slot = 0; slot < config::toolCount; ++slot) {
logic::LoadFilament lf;
LoadFilamentCommonSetup(slot, lf);
LoadFilamentCommonSetup(slot, lf, false);
FailedLoadToFinda(slot, lf);
FailedLoadToFindaResolveManualNoFINDA(slot, lf);
}
@ -268,7 +273,7 @@ TEST_CASE("load_filament::failed_load_to_finda_0-4_resolve_manual_no_FINDA", "[l
TEST_CASE("load_filament::failed_load_to_finda_0-4_try_again", "[load_filament]") {
for (uint8_t slot = 0; slot < config::toolCount; ++slot) {
logic::LoadFilament lf;
LoadFilamentCommonSetup(slot, lf);
LoadFilamentCommonSetup(slot, lf, false);
FailedLoadToFinda(slot, lf);
FailedLoadToFindaResolveTryAgain(slot, lf);
}

View File

@ -10,9 +10,9 @@ Motion motion;
// Intentionally inited with strange values
// Need to call ReinitMotion() each time we start some unit test
AxisSim axes[3] = {
{ -32767, -32767, false, false, false }, // pulley
{ -32767, -32767, false, false, false }, // selector //@@TODO proper selector positions once defined
{ -32767, -32767, false, false, false }, // idler
{ -32767, false, false, false, {} }, // pulley
{ -32767, false, false, false, {} }, // selector //@@TODO proper selector positions once defined
{ -32767, false, false, false, {} }, // idler
};
bool Motion::InitAxis(Axis axis) {
@ -37,7 +37,7 @@ void TriggerStallGuard(Axis axis) {
}
void Motion::PlanMoveTo(Axis axis, pos_t pos, steps_t feed_rate, steps_t end_rate) {
axes[axis].targetPos = pos;
axes[axis].plannedMoves.push_back(pos);
if (!axisData[axis].enabled)
SetEnabled(axis, true);
}
@ -60,10 +60,15 @@ void Motion::SetMode(Axis axis, hal::tmc2130::MotorMode mode) {
st_timer_t Motion::Step() {
for (uint8_t i = 0; i < 3; ++i) {
if (axes[i].pos != axes[i].targetPos) {
int8_t dirInc = (axes[i].pos < axes[i].targetPos) ? 1 : -1;
axes[i].pos += dirInc;
axisData[i].ctrl.SetPosition(axes[i].pos);
if (!axes[i].plannedMoves.empty()) {
pos_t axisTargetPos = axes[i].plannedMoves.front();
if (axes[i].pos != axisTargetPos) {
int8_t dirInc = (axes[i].pos < axisTargetPos) ? 1 : -1;
axes[i].pos += dirInc;
axisData[i].ctrl.SetPosition(axes[i].pos);
} else if (!axes[i].plannedMoves.empty()) {
axes[i].plannedMoves.pop_front(); // one move completed, plan the next one
}
}
}
return 0;
@ -71,14 +76,18 @@ st_timer_t Motion::Step() {
bool Motion::QueueEmpty() const {
for (uint8_t i = 0; i < 3; ++i) {
if (axes[i].pos != axes[i].targetPos)
if (!axes[i].plannedMoves.empty())
return false;
}
return true;
}
bool Motion::QueueEmpty(Axis axis) const {
return axes[axis].pos == axes[axis].targetPos;
return axes[axis].plannedMoves.empty();
}
uint8_t Motion::PlannedMoves(Axis axis) const {
return axes[axis].plannedMoves.size();
}
void Motion::AbortPlannedMoves(bool halt) {
@ -88,25 +97,31 @@ void Motion::AbortPlannedMoves(bool halt) {
}
void Motion::AbortPlannedMoves(config::Axis i, bool) {
axes[i].targetPos = axes[i].pos; // leave the axis where it was at the time of abort
axes[i].plannedMoves.clear(); // leave the axis where it was at the time of abort
axisData[i].ctrl.SetPosition(axes[i].pos);
}
void ReinitMotion() {
// reset the simulation data to defaults
axes[0] = AxisSim({ 0, 0, false, false, false }); // pulley
axes[0] = AxisSim({ 0, false, false, false, {} }); // pulley
axes[1] = AxisSim({ unitToSteps<S_pos_t>(config::selectorSlotPositions[0]),
unitToSteps<S_pos_t>(config::selectorSlotPositions[0]),
false, false, false }); // selector
false, false, false, {} }); // selector
axes[2] = AxisSim({ unitToSteps<I_pos_t>(config::idlerSlotPositions[mi::Idler::IdleSlotIndex()]),
unitToSteps<I_pos_t>(config::idlerSlotPositions[mi::Idler::IdleSlotIndex()]),
false, false, false }); // idler
false, false, false, {} }); // idler
}
bool PulleyEnabled() {
return axes[0].enabled;
}
pos_t AxisNearestTargetPos(Axis axis) {
if (axes[axis].plannedMoves.empty()) {
return axes[axis].pos;
} else {
return axes[axis].plannedMoves.front();
}
}
/// probably higher-level operations knowing the semantic meaning of axes
} // namespace motion

View File

@ -1,15 +1,16 @@
#pragma once
#include <stdint.h>
#include <deque>
namespace modules {
namespace motion {
struct AxisSim {
pos_t pos;
pos_t targetPos;
bool enabled;
bool homed;
bool stallGuard;
std::deque<pos_t> plannedMoves;
};
extern AxisSim axes[3];
@ -17,6 +18,7 @@ extern AxisSim axes[3];
void ReinitMotion();
bool PulleyEnabled();
void TriggerStallGuard(Axis axis);
pos_t AxisNearestTargetPos(Axis axis);
} // namespace motion
} // namespace modules

View File

@ -303,9 +303,11 @@ void FailedUnloadResolveManual(uint8_t slot, logic::UnloadFilament &uf) {
// we still need to feed to FINDA and back to verify the position of the filament
SimulateIdlerHoming(uf);
REQUIRE(WhileTopState(uf, ProgressCode::FeedingToFinda, 5000));
REQUIRE(WhileCondition(uf, std::bind(SimulateFeedToFINDA, _1, 100), 5000));
REQUIRE(WhileTopState(uf, ProgressCode::RetractingFromFinda, idlerEngageDisengageMaxSteps));
REQUIRE(WhileCondition(uf, std::bind(SimulateRetractFromFINDA, _1, 100), 5000));
REQUIRE(WhileCondition(
uf, [&](uint32_t) { return uf.State() == ProgressCode::RetractingFromFinda; }, 50000));
REQUIRE(VerifyState(uf, mg::FilamentLoadState::AtPulley, config::toolCount, config::toolCount, false, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::DisengagingIdler));
SimulateSelectorHoming(uf);
@ -367,8 +369,13 @@ TEST_CASE("unload_filament::unload_homing_retry", "[unload_filament][homing]") {
hal::gpio::WritePin(FINDA_PIN, hal::gpio::Level::low);
PressButtonAndDebounce(uf, mb::Right);
SimulateIdlerHoming(uf); // make Idler happy
REQUIRE(WhileTopState(uf, ProgressCode::FeedingToFinda, 5000));
REQUIRE(WhileTopState(uf, ProgressCode::RetractingFromFinda, idlerEngageDisengageMaxSteps));
REQUIRE(WhileCondition(uf, std::bind(SimulateFeedToFINDA, _1, 100), 5000));
REQUIRE(WhileCondition(uf, std::bind(SimulateRetractFromFINDA, _1, 100), 5000));
REQUIRE(WhileCondition(
uf, [&](uint32_t) { return uf.State() == ProgressCode::RetractingFromFinda; }, 50000));
REQUIRE(WhileTopState(uf, ProgressCode::DisengagingIdler, idlerEngageDisengageMaxSteps));
// now fail homing of the Selector

View File

@ -44,8 +44,8 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
// it should have instructed the selector and idler to move to slot 1
// check if the idler and selector have the right command
CHECK(mm::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0).v);
CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
// engaging idler
REQUIRE(WhileCondition(
@ -96,8 +96,8 @@ TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]")
// it should have instructed the selector and idler to move to slot 1
// check if the idler and selector have the right command
CHECK(mm::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0).v);
CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
CHECK(mm::axes[mm::Idler].enabled == true);
// engaging idler
@ -137,8 +137,8 @@ TEST_CASE("unload_to_finda::unload_without_FSensor_trigger", "[unload_to_finda]"
// it should have instructed the selector and idler to move to slot 1
// check if the idler and selector have the right command
CHECK(mm::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0).v);
CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
CHECK(mm::axes[mm::Idler].enabled == true);
// engaging idler