LUMIERA.clone/tests/core/steam/engine/mock-dispatcher.hpp
Ichthyostega f6af4c6a16 Dispatcher-Pipeline: prepare test for the new NodeGraphAttachment
It turns out that the real (not mocked) implementation of JobTicket creation
is already required now for this planned (mock)Dispatcher setup;
moreover, this real implementation turns out to be almost identical
to the mock implementation written recently -- just nested structure
of prerequiste JobTickets need to be changed into a similar structur
of ExitNodes

-- as an aside: rearrange various tests to be more in-line
   with the envisioned architecture of playback and engine
2023-06-07 04:03:00 +02:00

342 lines
12 KiB
C++

/*
MOCK-DISPATCHER.hpp - test scaffolding to verify render job planning and dispatch
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 mock-dispatcher.hpp
** Mock data structures to support implementation testing of render job
** planning and frame dispatch. Together with dummy-job.hpp, this provides
** a specifically rigged test setup, allowing to investigate and verify
** designated functionality in isolation, without backing by the actual
** render engine implementation.
** - the MockDispatcherTable emulates the frame dispatch from the Fixture
** - MockSegmentation is a mocked variant of the »Segmentation« datastructure,
** which forms the backbone of the Fixture and is the top-level attachment
** point for the »low-level-Model« (the render nodes network)
** - MockJobTicket is a builder / adapter on top of the actual steam::engine::JobTicket,
** allowing to generate a complete rigged MockSegmentation setup from a generic
** test specification written as nested lib::diff::GenNode elements. From this
** setup, »mock jobs« can be generated, which use the DummyJob functor and
** just record any invocation without performing actual work.
** @remark in spring 2023, this setup was created as a means to define and
** then build the actual implementation of frame dispatch and scheduling.
** @see MockSupport_test
*/
#ifndef STEAM_ENGINE_TEST_MOCK_DISPATCHER_H
#define STEAM_ENGINE_TEST_MOCK_DISPATCHER_H
#include "lib/test/test-helper.hpp"
#include "steam/play/dummy-play-connection.hpp"
#include "steam/fixture/node-graph-attachment.hpp"
#include "steam/fixture/segmentation.hpp"
#include "steam/mobject/model-port.hpp"
#include "steam/engine/dispatcher.hpp"
#include "steam/engine/job-ticket.hpp"
#include "vault/engine/job.h"
#include "vault/engine/dummy-job.hpp"
#include "vault/real-clock.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/linked-elements.hpp"
#include "lib/itertools.hpp"
#include "lib/depend.hpp"
#include <tuple>
#include <list>
namespace steam {
namespace engine {
namespace test {
using std::make_tuple;
using lib::diff::GenNode;
using lib::diff::MakeRec;
using lib::time::TimeValue;
using lib::time::Time;
using lib::HashVal;
///////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1294 : organisation of namespaces / includes??
using fixture::Segmentation;
namespace { // used internally
using play::test::ModelPorts;
using play::test::PlayTestFrames_Strategy;
using vault::engine::JobClosure;
using vault::engine::JobParameter;
using vault::engine::DummyJob;
using DummyPlaybackSetup = play::test::DummyPlayConnection<PlayTestFrames_Strategy>;
/* ===== specify a mock JobTicket setup for tests ===== */
template<class IT>
inline auto
defineSpec (HashVal seed, IT&& prereq)
{
using SpecTuple = std::tuple<JobFunctor&, HashVal, IT>;
return lib::singleValIterator( /////////////////////////////////////////////TICKET #1297 : multiplicity per channel will be removed here
SpecTuple(DummyJob::getFunctor()
, seed
, std::forward<IT> (prereq)));
}
inline auto
defineSimpleSpec (HashVal seed =0)
{
return defineSpec (seed, lib::nilIterator<JobTicket&>());
}
}//(End)internal test helpers....
/**
* Mock setup for a JobTicket to generate DummyJob invocations.
* Implemented as subclass, it provides a specification DSL for tests,
* and is able to probe some otherwise opaque internals of JobTicket.
* Beyond that, MockJobTicket has the same storage size; and behaves
* like the regular JobTicket after construction -- but any Job
* created by JobTicket::createJobFor(FrameCoord) will be wired
* with the DummyJob functor and can thus be related back to
* the test specification setup.
* @see JobPlanningSetup_test
* @see DispatcherInterface_test
*/
class MockJobTicket
: public JobTicket
{
public:
MockJobTicket()
: JobTicket{defineSimpleSpec()}
{ }
MockJobTicket (HashVal seed)
: JobTicket{defineSimpleSpec (seed)}
{ }
template<class IT>
MockJobTicket (HashVal seed, IT&& prereq)
: JobTicket{defineSpec (seed, std::forward<IT> (prereq))}
{ }
/* ===== Diagnostics ===== */
bool verify_associated (Job const&) const;
static bool isAssociated (Job const&, JobTicket const&);
};
/**
* Mock setup for a complete Segmentation to emulate the structure
* of the actual fixture, without the need of building a low-level Model.
* MockSegmentation instances can be instantiated directly within the
* test, by passing a test specification in »GenNode« notation to the
* constructor. This specification defines the segments to create
* and allows to associate a marker number, which can later be
* verified from the actual DummyJob invocation.
* - the ctor accepts a sequence of GenNode elements,
* each corresponding to a segment to created
* - optionally, attributes "start" and "after" can be defined
* to provide the lib::time::Time values of segment start/end
* - in addition, optionally a "mark" attribute can be defined;
* the given integer number will be "hidden" in the job instance
* hash, and can be [verified](\ref DummyJob::invocationAdditionalKey)
* - the _scope_ of each top-level GenNode may hold a sequence of
* nested nodes corresponding to _prerequisite_ JobTicket instances
* - these can in turn hold further nested prerequisites, and so on
* @see MockSetup_test::verify_MockSegmentation
*/
class MockSegmentation
: public Segmentation
{
// simulated allocator;
// must be able to handle re-entrant allocations
std::list<MockJobTicket> tickets_;
public:
MockSegmentation()
: Segmentation{}
, tickets_{}
{ }
MockSegmentation (std::initializer_list<GenNode> specs)
: MockSegmentation{}
{
for (auto& spec : specs)
{
JobTicket& newTicket = buildTicketFromSpec (spec);
auto start = spec.retrieveAttribute<Time> ("start");
auto after = spec.retrieveAttribute<Time> ("after");
Segmentation::splitSplice (start, after, &newTicket);
}
}
ExitNode
buildExitNodeFromSpec (GenNode const& spec)
{
UNIMPLEMENTED ("rewrite the mock builder code to generate a network of ExitNodes instead of JobTickets");
}
private: /* ======== Implementation: build mock JobTickes from test specification ==== */
HashVal
buildSeed (GenNode const& spec)
{
auto seed = spec.retrieveAttribute<int> ("mark");
return seed? HashVal(*seed) : HashVal(rand() % 1000);
}
auto
buildPrerequisites (GenNode const& spec)
{
return lib::transformIterator (spec.getChildren()
,[this](GenNode const& childSpec) -> JobTicket&
{
return buildTicketFromSpec (childSpec);
});
}
JobTicket&
buildTicketFromSpec (GenNode const& spec)
{
return tickets_.emplace_back (buildSeed(spec)
,buildPrerequisites(spec));
} // Warning: re-entrant invocation of emplace_back
};
/**
* verify the given job instance was actually generated from this JobTicket.
* @remark this test support function relies on some specific rigging,
* which typically is prepared by setup of a MockJobTicket.
*/
inline bool
MockJobTicket::verify_associated (Job const& job) const
{
JobFunctor& functor = dynamic_cast<JobFunctor&> (static_cast<JobClosure&> (*job.jobClosure));
Time nominalTime {TimeValue{job.parameter.nominalTime}};
InvocationInstanceID const& invoKey = job.parameter.invoKey;
return this->isValid()
and this->verifyInstance(functor, invoKey, nominalTime);
}
/**
* convenience shortcut to perform [this test](\ref MockJobTicket::verify_associated)
* on arbitrary JobTicket and Job instances.
* @warning a positive test result however relies on some casting trickery and there is no
* guarantee this test works if the JobTicket was not created from this mock framework.
*/
inline bool
MockJobTicket::isAssociated (Job const& job, JobTicket const& ticket)
{ // should work always, since storage is the same
MockJobTicket const& backdoor = static_cast<MockJobTicket const&> (ticket);
return backdoor.verify_associated (job);
}
class MockDispatcher
: public Dispatcher
{
DummyPlaybackSetup dummySetup_;
MockSegmentation mockSeg_;
/* == mock Dispatcher implementation == */
FrameCoord
locateRelative (FrameCoord const&, FrameCnt frameOffset)
{
UNIMPLEMENTED ("dummy implementation of the core dispatch operation");
}
bool
isEndOfChunk (FrameCnt, ModelPort port)
{
UNIMPLEMENTED ("determine when to finish a planning chunk");
}
JobTicket&
accessJobTicket (ModelPort, TimeValue nominalTime)
{
UNIMPLEMENTED ("dummy implementation of the model backbone / segmentation");
}
public:
MockDispatcher() = default;
MockDispatcher (std::initializer_list<GenNode> specs)
: mockSeg_(specs)
{ }
ModelPort
provideMockModelPort()
{
ModelPorts mockModelPorts = dummySetup_.getAllModelPorts();
return *mockModelPorts; // using just the first dummy port
}
/**
* The faked builder/playback setup provides some preconfigured ModelPort and
* corresponding DataSink handles. These are stored into a dummy registry and only available
* during the lifetime of the DummyPlaybackSetup instance.
* @param index number of the distinct port / connection
* @return a `std::pair<ModelPort,DataSink>`
* @warning as of 5/2023, there are two preconfigured "slots",
* and they are not usable in any way other then refering to their identity
*/
play::test::DummyOutputLink
getDummyConnection(uint index)
{
return dummySetup_.getModelPort (index);
}
/**
* Test support: verify the given Job is consistent with this Dispatcher.
*/
bool verify(Job const& job, ModelPort const& port, play::DataSink const& sink)
{
UNIMPLEMENTED ("verify the job was plausibly created from this dispatcher");
}
};
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1221
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1221
}}} // namespace steam::engine::test
#endif /*STEAM_ENGINE_TEST_MOCK_DISPATCHER_H*/