- fix a bug in IterExplorer: when iterating a »state core« directly, the helper CoreYield passed the detected type through ValueTypeBindings. This is logically wrong, because we never want to pick up some typedefs, rather we always want to use the type directly returned from CORE::yield() Here the iterator returns an Epoch&, which itself is again iterable (it inherits from std::array<Activity, N>). However, it is clear that we must not descent into such a "flatMap" style recursive expansion - draft a simple scheme how to regulate Epoch lengths dynamically - add diagnostics to pinpoint a given Activity and find out into which Epoch it has been allocated; used to cover the allocator behaviour
326 lines
12 KiB
C++
326 lines
12 KiB
C++
/*
|
|
BlockFlow(Test) - verify scheduler memory management scheme
|
|
|
|
Copyright (C) Lumiera.org
|
|
2023, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
* *****************************************************/
|
|
|
|
/** @file block-flow-test.cpp
|
|
** unit test \ref BlockFlow_test
|
|
*/
|
|
|
|
|
|
#include "lib/test/run.hpp"
|
|
#include "lib/test/test-helper.hpp"
|
|
#include "vault/gear/block-flow.hpp"
|
|
//#include "lib/time/timevalue.hpp"
|
|
//#include "lib/format-cout.hpp"
|
|
#include "lib/test/diagnostic-output.hpp" ////////////////////////////////TODO
|
|
#include "lib/util.hpp"
|
|
|
|
//#include <utility>
|
|
|
|
using test::Test;
|
|
//using std::move;
|
|
using util::isSameObject;
|
|
using lib::test::randTime;
|
|
using lib::test::showType;
|
|
|
|
|
|
namespace vault{
|
|
namespace gear {
|
|
namespace test {
|
|
|
|
// using lib::time::FrameRate;
|
|
// using lib::time::Offset;
|
|
// using lib::time::Time;
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************************//**
|
|
* @test document the memory management scheme used by the Scheduler.
|
|
* @see SchedulerActivity_test
|
|
* @see SchedulerUsage_test
|
|
*/
|
|
class BlockFlow_test : public Test
|
|
{
|
|
|
|
virtual void
|
|
run (Arg)
|
|
{
|
|
simpleUsage();
|
|
verifyAPI();
|
|
handleEpoch();
|
|
placeActivity();
|
|
adjustEpochs();
|
|
storageFlow();
|
|
}
|
|
|
|
|
|
/** @test demonstrate a simple usage scenario
|
|
* - open new Epoch to allocate an Activity
|
|
* - clean-up at a future time point
|
|
*/
|
|
void
|
|
simpleUsage()
|
|
{
|
|
BlockFlow bFlow;
|
|
Time deadline = randTime();
|
|
|
|
Activity& tick = bFlow.until(deadline).create();
|
|
CHECK (tick.verb_ == Activity::TICK);
|
|
CHECK (1 == watch(bFlow).cntEpochs());
|
|
CHECK (watch(bFlow).first() > deadline);
|
|
CHECK (watch(bFlow).first() - deadline == bFlow.getEpochStep());
|
|
|
|
bFlow.discardBefore (deadline + Time{0,5});
|
|
CHECK (0 == watch(bFlow).cntEpochs());
|
|
}
|
|
|
|
|
|
|
|
/** @test verify the primary BlockFlow API functions in isolation
|
|
* @todo WIP 7/23 ⟶ define ⟶ implement
|
|
*/
|
|
void
|
|
verifyAPI()
|
|
{
|
|
//SHOW_EXPR(watch(bFlow).cntEpochs());
|
|
//SHOW_EXPR(watch(bFlow).poolSize());
|
|
//SHOW_EXPR(watch(bFlow).first());
|
|
}
|
|
|
|
|
|
|
|
/** @test cover properties and handling of Epochs (low-level)
|
|
* - demonstrate that Epoch is placed into an Extent
|
|
* - verify that both Extent and Epoch access the same memory block
|
|
* - demonstrate the standard setup and initialisation of an Epoch
|
|
* - allocate some Activities into the storage and observe free-managment
|
|
* - detect when the Epoch is filled up
|
|
* - verify alive / dead decision relative to given deadline
|
|
* @note this test covers helpers and implementation structures of BlockFlow,
|
|
* without actually using a BlockFlow instance; rather, the typical handling
|
|
* and low-level bookkeeping aspects are emulated and observed
|
|
*/
|
|
void
|
|
handleEpoch()
|
|
{
|
|
using Extent = Allocator::Extent;
|
|
// the raw storage Extent is a compact block
|
|
// providing uninitialised storage typed as `vault::gear::Activity`
|
|
|
|
Allocator alloc;
|
|
alloc.openNew();
|
|
Extent& extent = *alloc.begin();
|
|
CHECK (extent.size() == Extent::SIZ::value);
|
|
CHECK (sizeof(extent) == extent.size() * sizeof(Activity));
|
|
CHECK (showType<Extent::value_type>() == "vault::gear::Activity"_expect);
|
|
|
|
// we can just access some slot and place data there
|
|
extent[55].data_.feed.one = 555555555555555;
|
|
|
|
// now establish an Epoch in this storage block:
|
|
Epoch& epoch = Epoch::setup (alloc.begin(), Time{0,10});
|
|
|
|
// the underlying storage is not touched yet...
|
|
CHECK (epoch[55].data_.feed.one == 555555555555555);
|
|
|
|
// but in the first slot, an »EpochGate« has been implanted
|
|
Epoch::EpochGate& gate = epoch.gate();
|
|
CHECK (isSameObject (gate, epoch[0]));
|
|
CHECK (isSameObject (epoch[0], extent[0]));
|
|
CHECK (Time{gate.deadline()} == Time(0,10));
|
|
CHECK (Time{gate.deadline()} == Time{epoch[0].data_.condition.dead});
|
|
CHECK (Activity::GATE == epoch[0].verb_);
|
|
|
|
// the gate's `next`-pointer is (ab)used to manage the next allocation slot
|
|
CHECK (isSameObject (*gate.next, epoch[extent.size()-1]));
|
|
|
|
// the storage there is not yet used, but will be overwritten by the ctor call
|
|
epoch[extent.size()-1].data_.timing.instant = Time{5,5};
|
|
|
|
// allocate a new Activity into the next free slot
|
|
BlockFlow::AllocatorHandle allocHandle{alloc.begin()};
|
|
Activity& timeStart = allocHandle.create (Activity::TIMESTART);
|
|
CHECK (isSameObject (timeStart, epoch[extent.size()-1]));
|
|
|
|
// this Activity object is properly initialised (and memory was altered)
|
|
CHECK (epoch[extent.size()-1].data_.timing.instant != Time(5,5));
|
|
CHECK (epoch[extent.size()-1].data_.timing.instant == Time::NEVER);
|
|
CHECK (timeStart.verb_ == Activity::TIMESTART);
|
|
CHECK (timeStart.data_.timing.instant == Time::NEVER);
|
|
CHECK (timeStart.data_.timing.quality == 0);
|
|
|
|
// and the free-pointer was decremented to point to the next free slot
|
|
CHECK (isSameObject (*gate.next, epoch[extent.size()-2]));
|
|
|
|
// which also implies that there is still ample space left...
|
|
CHECK (gate.hasFreeSlot());
|
|
|
|
// so let's eat this space up...
|
|
for (uint i=extent.size()-2; i>1; --i)
|
|
allocHandle.create();
|
|
|
|
// one final slot is left (beyond of the EpochGate itself)
|
|
CHECK (isSameObject (*gate.next, epoch[1]));
|
|
CHECK (gate.hasFreeSlot());
|
|
|
|
allocHandle.create (size_t(111), size_t(222));
|
|
CHECK (epoch[1].verb_ == Activity::FEED);
|
|
CHECK (epoch[1].data_.feed.one = 111);
|
|
CHECK (epoch[1].data_.feed.two = 222);
|
|
|
|
// aaand the boat is full...
|
|
CHECK (not gate.hasFreeSlot());
|
|
CHECK (isSameObject (*gate.next, epoch[0]));
|
|
|
|
// a given Epoch can be checked for relevance against a deadline
|
|
CHECK (gate.deadline() == Time(0,10));
|
|
|
|
CHECK ( gate.isAlive (Time(0,5)));
|
|
CHECK ( gate.isAlive (Time(999,9)));
|
|
CHECK (not gate.isAlive (Time(0,10)));
|
|
CHECK (not gate.isAlive (Time(1,10)));
|
|
////////////////////////////////////////////////////////////////////////////////////////TICKET #1298 : actually use a GATE implementation and then also check the count-down latch
|
|
}
|
|
|
|
|
|
|
|
/** @test TODO place Activity record into storage
|
|
* @todo WIP 7/23 ⟶ ✔define ⟶ 🔁implement
|
|
*/
|
|
void
|
|
placeActivity()
|
|
{
|
|
BlockFlow bFlow;
|
|
|
|
Time t1 = Time{ 0,10};
|
|
Time t2 = Time{500,10};
|
|
Time t3 = Time{ 0,11};
|
|
|
|
auto& a1 = bFlow.until(t1).create();
|
|
CHECK (watch(bFlow).allEpochs() == "10s200ms"_expect);
|
|
CHECK (watch(bFlow).find(a1) == "10s200ms"_expect);
|
|
|
|
auto& a3 = bFlow.until(t3).create();
|
|
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s0ms"_expect);
|
|
CHECK (watch(bFlow).find(a3) == "11s0ms"_expect);
|
|
|
|
auto& a2 = bFlow.until(t2).create();
|
|
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s00ms"_expect);
|
|
CHECK (watch(bFlow).find(a2) == "11s600ms"_expect);
|
|
|
|
Time t0 = Time{0,5};
|
|
|
|
auto& a0 = bFlow.until(t0).create();
|
|
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s00ms"_expect);
|
|
CHECK (watch(bFlow).find(a0) == "10s200ms"_expect);
|
|
|
|
BlockFlow::AllocatorHandle allocHandle = bFlow.until(Time{300,10});
|
|
for (uint i=1; i<Epoch::SIZ(); ++i)
|
|
allocHandle.create();
|
|
|
|
CHECK (allocHandle.currDeadline() == Time(400,10));
|
|
CHECK (not allocHandle.hasFreeSlot());
|
|
|
|
auto a4 = allocHandle.create();
|
|
CHECK (allocHandle.currDeadline() == Time(600,10));
|
|
CHECK (allocHandle.hasFreeSlot());
|
|
CHECK (watch(bFlow).find(a4) == "10s600ms"_expect);
|
|
|
|
for (uint i=1; i<Epoch::SIZ(); ++i)
|
|
allocHandle.create();
|
|
|
|
CHECK (allocHandle.currDeadline() == Time(800,10));
|
|
|
|
auto& a5 = bFlow.until(Time{220,10}).create();
|
|
CHECK (watch(bFlow).find(a5) == "10s600ms"_expect);
|
|
|
|
allocHandle = bFlow.until(Time{900,10});
|
|
for (uint i=1; i<Epoch::SIZ(); ++i)
|
|
allocHandle.create();
|
|
|
|
CHECK (not allocHandle.hasFreeSlot());
|
|
auto& a6 = bFlow.until(Time{850,10}).create();
|
|
CHECK (watch(bFlow).find(a6) == "11s150ms"_expect);
|
|
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s00ms|11s150ms"_expect);
|
|
|
|
auto& a7 = bFlow.until(Time{500,11}).create();
|
|
CHECK (watch(bFlow).find(a7) == "11s600ms"_expect);
|
|
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s00ms|11s150ms|11s300ms|11s450ms|11s600ms"_expect);
|
|
|
|
bFlow.discardBefore (Time{999,10});
|
|
CHECK (watch(bFlow).allEpochs() == "11s00ms|11s150ms|11s300ms|11s450ms|11s600ms"_expect);
|
|
|
|
auto& a8 = bFlow.until(Time{500,10}).create();
|
|
CHECK (watch(bFlow).find(a8) == "11s150ms"_expect);
|
|
}
|
|
|
|
|
|
|
|
/** @test TODO load based regulation of Epoch spacing
|
|
* @todo WIP 7/23 ⟶ 🔁define ⟶ implement
|
|
*/
|
|
void
|
|
adjustEpochs()
|
|
{
|
|
BlockFlow bFlow;
|
|
CHECK (bFlow.getEpochStep() == INITIAL_EPOCH_STEP);
|
|
|
|
bFlow.markEpochOverflow();
|
|
CHECK (bFlow.getEpochStep() == INITIAL_EPOCH_STEP * OVERFLOW_BOOST_FACTOR);
|
|
bFlow.markEpochOverflow();
|
|
CHECK (bFlow.getEpochStep() == INITIAL_EPOCH_STEP * OVERFLOW_BOOST_FACTOR*OVERFLOW_BOOST_FACTOR);
|
|
|
|
Duration dur1 = INITIAL_EPOCH_STEP;
|
|
Duration dur2 = INITIAL_EPOCH_STEP * OVERFLOW_BOOST_FACTOR;
|
|
|
|
TimeVar step = bFlow.getEpochStep();
|
|
Rat fill = 8_r/10;
|
|
Rat N = AVERAGE_EPOCHS;
|
|
|
|
bFlow.markEpochUnderflow (dur1, fill);
|
|
CHECK (bFlow.getEpochStep() == step*((N-1)/N) + dur1*(1/N /fill));
|
|
|
|
step = bFlow.getEpochStep();
|
|
fill = 3_r/10;
|
|
bFlow.markEpochUnderflow (dur2, fill);
|
|
CHECK (bFlow.getEpochStep() == step*((N-1)/N) + dur2*(1/N /fill));
|
|
}
|
|
|
|
|
|
|
|
/** @test TODO maintain progression of epochs.
|
|
* @todo WIP 7/23 ⟶ define ⟶ implement
|
|
*/
|
|
void
|
|
storageFlow()
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
/** Register this test class... */
|
|
LAUNCHER (BlockFlow_test, "unit engine");
|
|
|
|
|
|
|
|
}}} // namespace vault::gear::test
|