2023-04-17 17:10:53 +02:00
|
|
|
|
/*
|
2023-05-24 03:38:12 +02:00
|
|
|
|
MOCK-DISPATCHER.hpp - test scaffolding to verify render job planning and dispatch
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
Copyright (C)
|
|
|
|
|
|
2023, Hermann Vosseler <Ichthyostega@web.de>
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
**Lumiera** 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. See the file COPYING for further details.
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
/** @file mock-dispatcher.hpp
|
|
|
|
|
|
** Mock data structures to support implementation testing of render job
|
2023-06-13 20:23:33 +02:00
|
|
|
|
** planning and frame dispatch. This specifically rigged test setup allows to
|
|
|
|
|
|
** investigate and verify designated functionality in isolation, without backing
|
|
|
|
|
|
** by the actual render engine and low-level-Model implementation.
|
|
|
|
|
|
** - a MockJob is a render Job, wired to a DummyFunctor, which does nothing,
|
|
|
|
|
|
** but records any invocation into an internal diagnostics Map.
|
|
|
|
|
|
** - MockJobTicket is a builder / adapter on top of the actual steam::engine::JobTicket,
|
|
|
|
|
|
** allowing to generate simple JobTicket instances with an embedded ExitNode and
|
|
|
|
|
|
** a (configurable) pipelineID. From this setup, »mock jobs« can be generated,
|
|
|
|
|
|
** which use the MockJob functor and thus record any invocation without performing
|
|
|
|
|
|
** actual work. The internal connection to the MockJobTicket can then be verified.
|
2023-05-24 03:38:12 +02:00
|
|
|
|
** - MockSegmentation is a mocked variant of the »Segmentation« datastructure,
|
|
|
|
|
|
** which forms the backbone of the Fixture and is the top-level attachment
|
2023-06-13 20:23:33 +02:00
|
|
|
|
** point for the »low-level-Model« (the render nodes network). It can be
|
|
|
|
|
|
** configured with a test specification of ExitNode(s) defined by a
|
|
|
|
|
|
** GenNode tree, and defining Segments of the timeline and prerequisites.
|
|
|
|
|
|
** - finally, the MockDispatcher combines all these facilities to emulate
|
|
|
|
|
|
** frame dispatch from the Fixture without actually using any data model.
|
|
|
|
|
|
** Similar to MockSegmentation, a GenNode-based specification is used.
|
|
|
|
|
|
**
|
2023-05-24 03:38:12 +02:00
|
|
|
|
** @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
|
2023-04-17 17:10:53 +02:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
#ifndef STEAM_ENGINE_TEST_MOCK_DISPATCHER_H
|
|
|
|
|
|
#define STEAM_ENGINE_TEST_MOCK_DISPATCHER_H
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
#include "lib/test/test-helper.hpp"
|
2023-06-02 02:31:34 +02:00
|
|
|
|
#include "steam/play/dummy-play-connection.hpp"
|
2023-06-07 04:03:00 +02:00
|
|
|
|
#include "steam/fixture/node-graph-attachment.hpp"
|
2023-04-25 18:27:16 +02:00
|
|
|
|
#include "steam/fixture/segmentation.hpp"
|
2023-04-17 17:10:53 +02:00
|
|
|
|
#include "steam/mobject/model-port.hpp"
|
|
|
|
|
|
#include "steam/engine/dispatcher.hpp"
|
2023-04-18 20:02:36 +02:00
|
|
|
|
#include "steam/engine/job-ticket.hpp"
|
2023-06-24 03:14:17 +02:00
|
|
|
|
#include "vault/gear/job.h"
|
2023-05-24 03:38:12 +02:00
|
|
|
|
#include "vault/real-clock.hpp"
|
2023-06-11 04:37:38 +02:00
|
|
|
|
#include "lib/allocator-handle.hpp"
|
2023-04-20 23:55:02 +02:00
|
|
|
|
#include "lib/time/timevalue.hpp"
|
2023-04-25 13:40:20 +02:00
|
|
|
|
#include "lib/diff/gen-node.hpp"
|
2023-05-23 06:40:18 +02:00
|
|
|
|
#include "lib/linked-elements.hpp"
|
2023-04-20 18:53:17 +02:00
|
|
|
|
#include "lib/itertools.hpp"
|
2023-05-24 03:38:12 +02:00
|
|
|
|
#include "lib/depend.hpp"
|
2023-06-13 03:47:42 +02:00
|
|
|
|
#include "lib/util.hpp"
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
2023-04-20 23:55:02 +02:00
|
|
|
|
#include <tuple>
|
2023-06-13 03:47:42 +02:00
|
|
|
|
#include <map>
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace steam {
|
|
|
|
|
|
namespace engine {
|
|
|
|
|
|
namespace test {
|
|
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
using std::make_tuple;
|
2023-04-25 13:40:20 +02:00
|
|
|
|
using lib::diff::GenNode;
|
|
|
|
|
|
using lib::diff::MakeRec;
|
2023-04-20 23:55:02 +02:00
|
|
|
|
using lib::time::TimeValue;
|
2023-05-02 04:16:39 +02:00
|
|
|
|
using lib::time::Time;
|
2023-04-30 02:18:56 +02:00
|
|
|
|
using lib::HashVal;
|
2024-11-13 02:23:23 +01:00
|
|
|
|
using lib::ranHash;
|
|
|
|
|
|
using lib::rani;
|
2023-06-13 03:47:42 +02:00
|
|
|
|
using util::isnil;
|
|
|
|
|
|
using util::isSameObject;
|
2023-04-25 18:27:16 +02:00
|
|
|
|
using fixture::Segmentation;
|
2023-06-16 04:09:38 +02:00
|
|
|
|
using vault::RealClock;
|
2023-06-24 03:14:17 +02:00
|
|
|
|
using vault::gear::Job;
|
|
|
|
|
|
using vault::gear::JobClosure;
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
2023-06-13 20:23:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Mock setup for a render Job with NO action but built-in diagnostics.
|
|
|
|
|
|
* Each invocation of such a MockJob will be logged internally
|
|
|
|
|
|
* and can be investigated and verified afterwards.
|
|
|
|
|
|
*/
|
|
|
|
|
|
class MockJob
|
|
|
|
|
|
: public Job
|
2023-05-01 17:02:11 +02:00
|
|
|
|
{
|
2023-06-13 20:23:33 +02:00
|
|
|
|
static Job build(); ///< uses random job definition values
|
|
|
|
|
|
static Job build (Time nominalTime, int additionalKey);
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
MockJob()
|
|
|
|
|
|
: Job{build()}
|
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
MockJob (Time nominalTime, int additionalKey)
|
|
|
|
|
|
: Job{build (nominalTime,additionalKey)}
|
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool was_invoked (Job const& job);
|
|
|
|
|
|
static Time invocationTime (Job const& job);
|
|
|
|
|
|
static Time invocationNominalTime (Job const& job);
|
|
|
|
|
|
static int invocationAdditionalKey (Job const& job);
|
|
|
|
|
|
|
|
|
|
|
|
static bool isNopJob (Job const&);
|
|
|
|
|
|
static JobClosure& getFunctor();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-06-12 19:21:14 +02:00
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
|
|
|
|
|
|
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-06-13 20:23:33 +02:00
|
|
|
|
* Mock setup for a JobTicket to generate dummy render Job invocations.
|
2023-05-24 03:38:12 +02:00
|
|
|
|
* 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
|
2023-06-22 20:23:55 +02:00
|
|
|
|
* created by JobTicket::createJobFor(nominalTime) will be wired
|
2023-06-13 20:23:33 +02:00
|
|
|
|
* with the MockJob functor and can thus be related back to
|
2023-05-24 03:38:12 +02:00
|
|
|
|
* the test specification setup.
|
2023-06-17 03:10:57 +02:00
|
|
|
|
* @see JobPlanningPipeline_test
|
2023-04-17 17:10:53 +02:00
|
|
|
|
* @see DispatcherInterface_test
|
|
|
|
|
|
*/
|
|
|
|
|
|
class MockJobTicket
|
2023-06-11 04:37:38 +02:00
|
|
|
|
: private lib::AllocatorHandle<JobTicket>
|
|
|
|
|
|
, public JobTicket
|
2023-04-17 17:10:53 +02:00
|
|
|
|
{
|
2023-06-11 04:37:38 +02:00
|
|
|
|
auto&
|
|
|
|
|
|
allocator()
|
|
|
|
|
|
{
|
|
|
|
|
|
return static_cast<lib::AllocatorHandle<JobTicket>&> (*this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-13 20:23:33 +02:00
|
|
|
|
/** provide a test specification wired to MockJob */
|
|
|
|
|
|
static ExitNode
|
2024-11-13 02:23:23 +01:00
|
|
|
|
defineSimpleSpec (HashVal seed =ranHash())
|
2023-06-13 20:23:33 +02:00
|
|
|
|
{
|
2023-06-21 03:55:09 +02:00
|
|
|
|
return ExitNode{seed, DUMMY_JOB_RUNTIME
|
2023-06-13 20:23:33 +02:00
|
|
|
|
,ExitNodes{}
|
|
|
|
|
|
,& MockJob::getFunctor()};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-17 17:10:53 +02:00
|
|
|
|
public:
|
2023-04-20 23:55:02 +02:00
|
|
|
|
MockJobTicket()
|
2023-06-11 04:37:38 +02:00
|
|
|
|
: JobTicket{defineSimpleSpec(), allocator()}
|
2023-05-01 17:02:11 +02:00
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
MockJobTicket (HashVal seed)
|
2023-06-11 04:37:38 +02:00
|
|
|
|
: JobTicket{defineSimpleSpec (seed), allocator()}
|
2023-05-01 17:02:11 +02:00
|
|
|
|
{ }
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
2023-05-11 22:47:56 +02:00
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
/* ===== Diagnostics ===== */
|
|
|
|
|
|
|
2023-04-30 02:18:56 +02:00
|
|
|
|
bool verify_associated (Job const&) const;
|
2023-05-01 17:02:11 +02:00
|
|
|
|
static bool isAssociated (Job const&, JobTicket const&);
|
2023-04-17 17:10:53 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 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
|
2023-06-13 20:23:33 +02:00
|
|
|
|
* verified from the actual DummyClosure invocation.
|
2023-05-24 03:38:12 +02:00
|
|
|
|
* - 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
|
2023-06-13 20:23:33 +02:00
|
|
|
|
* hash, and can be [verified](\ref MockJob::invocationAdditionalKey)
|
2023-05-24 03:38:12 +02:00
|
|
|
|
* - 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
|
|
|
|
|
|
*/
|
2023-04-25 13:40:20 +02:00
|
|
|
|
class MockSegmentation
|
|
|
|
|
|
: public Segmentation
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
MockSegmentation()
|
2023-04-27 22:30:49 +02:00
|
|
|
|
: Segmentation{}
|
2023-04-25 13:40:20 +02:00
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
MockSegmentation (std::initializer_list<GenNode> specs)
|
2023-04-27 19:38:37 +02:00
|
|
|
|
: MockSegmentation{}
|
2023-04-25 13:40:20 +02:00
|
|
|
|
{
|
2023-05-01 17:02:11 +02:00
|
|
|
|
for (auto& spec : specs)
|
|
|
|
|
|
{
|
2023-05-02 04:16:39 +02:00
|
|
|
|
auto start = spec.retrieveAttribute<Time> ("start");
|
|
|
|
|
|
auto after = spec.retrieveAttribute<Time> ("after");
|
2023-06-10 04:52:40 +02:00
|
|
|
|
Segmentation::splitSplice (start, after
|
|
|
|
|
|
,ExitNodes{buildExitNodeFromSpec (spec)}
|
|
|
|
|
|
);
|
2023-05-01 17:02:11 +02:00
|
|
|
|
}
|
2023-04-25 13:40:20 +02:00
|
|
|
|
}
|
2023-05-11 22:47:56 +02:00
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
|
2023-06-07 04:03:00 +02:00
|
|
|
|
ExitNode
|
|
|
|
|
|
buildExitNodeFromSpec (GenNode const& spec)
|
|
|
|
|
|
{
|
2023-06-07 17:21:31 +02:00
|
|
|
|
return ExitNode{buildSeed (spec)
|
2023-06-21 03:55:09 +02:00
|
|
|
|
,buildRuntime (spec)
|
2023-06-10 04:52:40 +02:00
|
|
|
|
,buildPrerequisites (spec)
|
2023-06-13 20:23:33 +02:00
|
|
|
|
,& MockJob::getFunctor()};
|
2023-06-10 04:52:40 +02:00
|
|
|
|
}
|
2023-06-07 04:03:00 +02:00
|
|
|
|
|
2023-06-13 03:47:42 +02:00
|
|
|
|
/** @internal helper for MockDispatcher */
|
|
|
|
|
|
void duplicateExitNodeSpec (uint times);
|
|
|
|
|
|
|
2023-06-07 04:03:00 +02:00
|
|
|
|
|
2023-06-07 17:21:31 +02:00
|
|
|
|
private: /* ======== Implementation: build fake ExitNodes from test specification ==== */
|
2023-05-24 03:38:12 +02:00
|
|
|
|
|
2023-05-11 22:47:56 +02:00
|
|
|
|
HashVal
|
|
|
|
|
|
buildSeed (GenNode const& spec)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto seed = spec.retrieveAttribute<int> ("mark");
|
2024-11-13 02:23:23 +01:00
|
|
|
|
return seed? HashVal(*seed) : HashVal(1 +rani(1000));
|
2023-05-11 22:47:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-21 03:55:09 +02:00
|
|
|
|
Duration
|
|
|
|
|
|
buildRuntime (GenNode const& spec)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto runtime = spec.retrieveAttribute<Duration> ("runtime");
|
|
|
|
|
|
return runtime? *runtime : DUMMY_JOB_RUNTIME;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-07 17:21:31 +02:00
|
|
|
|
ExitNodes
|
2023-05-11 22:47:56 +02:00
|
|
|
|
buildPrerequisites (GenNode const& spec)
|
|
|
|
|
|
{
|
2023-06-07 17:21:31 +02:00
|
|
|
|
ExitNodes prerequisites;
|
|
|
|
|
|
for (auto& child : spec.getChildren())
|
|
|
|
|
|
prerequisites.emplace_back (
|
|
|
|
|
|
buildExitNodeFromSpec (child));
|
|
|
|
|
|
return prerequisites;
|
2023-05-11 22:47:56 +02:00
|
|
|
|
}
|
2023-04-25 13:40:20 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
2023-05-24 03:38:12 +02:00
|
|
|
|
|
2023-06-13 03:47:42 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* This is some trickery to allow handling of multiple ModelPort(s) in MockDispatcher;
|
|
|
|
|
|
* actually the code using this mock setup does not need any elaborate differentiation
|
|
|
|
|
|
* of the ExitNodes structure per port, thus the first entry of the existing configuration
|
|
|
|
|
|
* is just duplicated for the given number of further ModelPorts.
|
|
|
|
|
|
* @warning this manipulation must be done prior to generating any JobTicket
|
|
|
|
|
|
*/
|
|
|
|
|
|
inline void
|
|
|
|
|
|
MockSegmentation::duplicateExitNodeSpec (uint times)
|
|
|
|
|
|
{
|
2023-06-13 20:23:33 +02:00
|
|
|
|
using Spec = fixture::NodeGraphAttachment;
|
|
|
|
|
|
|
|
|
|
|
|
Segmentation::adaptSpecification ([times](Spec const& spec)
|
|
|
|
|
|
{
|
2023-06-14 04:20:50 +02:00
|
|
|
|
return Spec{ExitNodes(times, spec[0])};
|
|
|
|
|
|
}); // vector with <times> copies of spec[0]
|
|
|
|
|
|
} // (Warning: use parens, not braces for this ctor...)
|
2023-06-13 03:47:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
2023-04-30 02:18:56 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* verify the given job instance was actually generated from this JobTicket.
|
2023-05-24 03:38:12 +02:00
|
|
|
|
* @remark this test support function relies on some specific rigging,
|
2023-04-30 02:18:56 +02:00
|
|
|
|
* 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}};
|
2023-05-01 14:07:21 +02:00
|
|
|
|
InvocationInstanceID const& invoKey = job.parameter.invoKey;
|
2023-04-30 02:18:56 +02:00
|
|
|
|
return this->isValid()
|
2023-05-01 14:07:21 +02:00
|
|
|
|
and this->verifyInstance(functor, invoKey, nominalTime);
|
2023-04-30 02:18:56 +02:00
|
|
|
|
}
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
2023-05-01 17:02:11 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* 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)
|
2023-05-24 03:38:12 +02:00
|
|
|
|
{ // should work always, since storage is the same
|
2023-05-01 17:02:11 +02:00
|
|
|
|
MockJobTicket const& backdoor = static_cast<MockJobTicket const&> (ticket);
|
|
|
|
|
|
return backdoor.verify_associated (job);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-17 17:10:53 +02:00
|
|
|
|
|
2023-06-02 02:31:34 +02:00
|
|
|
|
|
2023-06-13 20:23:33 +02:00
|
|
|
|
namespace { // used internally by MockDispatcher....
|
|
|
|
|
|
using play::test::ModelPorts;
|
|
|
|
|
|
using play::test::PlayTestFrames_Strategy;
|
|
|
|
|
|
|
|
|
|
|
|
using DummyPlaybackSetup = play::test::DummyPlayConnection<PlayTestFrames_Strategy>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* A mocked frame Dispatcher setup without any backing model.
|
|
|
|
|
|
* Instantiating such a MockDispatcher will automatically create some fake
|
|
|
|
|
|
* model structures and some ModelPort and DisplaySink handles (and thereby
|
|
|
|
|
|
* push aside and shadow any existing ModelPort registry).
|
|
|
|
|
|
*
|
|
|
|
|
|
* The configuration is similar to MockSegmentation, using a test spec
|
|
|
|
|
|
* given as GenNode-tree to define Segments of the timeline and possibly
|
|
|
|
|
|
* pipeline-IDs and prerequisites. One notable difference is that here
|
|
|
|
|
|
* the default ctor always creates a single Segment covering the whole
|
|
|
|
|
|
* time axis, and that the ExitNode specification is automatically
|
|
|
|
|
|
* duplicated for all faked ModelPort(s).
|
|
|
|
|
|
*/
|
2023-06-02 02:31:34 +02:00
|
|
|
|
class MockDispatcher
|
|
|
|
|
|
: public Dispatcher
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
DummyPlaybackSetup dummySetup_;
|
|
|
|
|
|
MockSegmentation mockSeg_;
|
|
|
|
|
|
|
2023-06-13 03:47:42 +02:00
|
|
|
|
using PortIdxMap = std::map<ModelPort, size_t>;
|
|
|
|
|
|
|
|
|
|
|
|
const PortIdxMap portIdx_;
|
2023-06-02 02:31:34 +02:00
|
|
|
|
|
2023-06-12 23:15:39 +02:00
|
|
|
|
public:
|
2023-06-13 20:23:33 +02:00
|
|
|
|
/* == mock implementation of the Dispatcher interface == */
|
2023-06-02 02:31:34 +02:00
|
|
|
|
|
2023-06-12 19:21:14 +02:00
|
|
|
|
size_t
|
|
|
|
|
|
resolveModelPort (ModelPort modelPort) override
|
|
|
|
|
|
{
|
2023-06-13 03:47:42 +02:00
|
|
|
|
auto entry = portIdx_.find(modelPort);
|
|
|
|
|
|
if (entry == portIdx_.end())
|
|
|
|
|
|
throw error::Logic{"Invalid ModelPort for this Dispatcher"};
|
|
|
|
|
|
else
|
|
|
|
|
|
return entry->second;
|
2023-06-12 19:21:14 +02:00
|
|
|
|
}
|
2023-06-13 03:47:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
2023-06-02 02:31:34 +02:00
|
|
|
|
JobTicket&
|
2023-06-18 03:50:48 +02:00
|
|
|
|
getJobTicketFor (size_t portIDX, TimeValue nominalTime) override
|
2023-06-02 02:31:34 +02:00
|
|
|
|
{
|
2023-06-13 03:47:42 +02:00
|
|
|
|
auto& seg = mockSeg_[nominalTime];
|
|
|
|
|
|
return seg.jobTicket(portIDX);
|
2023-06-02 02:31:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-06-13 20:23:33 +02:00
|
|
|
|
public:
|
2023-06-13 03:47:42 +02:00
|
|
|
|
MockDispatcher()
|
|
|
|
|
|
: dummySetup_{}
|
|
|
|
|
|
, mockSeg_{MakeRec().genNode()} // Node: generate a single active Segment to cover all
|
|
|
|
|
|
, portIdx_{buildPortIndex()}
|
|
|
|
|
|
{
|
|
|
|
|
|
mockSeg_.duplicateExitNodeSpec(portIdx_.size());
|
|
|
|
|
|
}
|
2023-06-02 02:31:34 +02:00
|
|
|
|
|
|
|
|
|
|
MockDispatcher (std::initializer_list<GenNode> specs)
|
2023-06-13 03:47:42 +02:00
|
|
|
|
: dummySetup_{}
|
|
|
|
|
|
, mockSeg_(specs)
|
|
|
|
|
|
, portIdx_{buildPortIndex()}
|
|
|
|
|
|
{
|
|
|
|
|
|
mockSeg_.duplicateExitNodeSpec(portIdx_.size());
|
|
|
|
|
|
}
|
2023-06-02 02:31:34 +02:00
|
|
|
|
|
2023-06-12 19:21:14 +02:00
|
|
|
|
|
2023-06-02 02:31:34 +02:00
|
|
|
|
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",
|
2023-06-13 03:47:42 +02:00
|
|
|
|
* and they are not usable in any way other then referring to their identity
|
2023-06-02 02:31:34 +02:00
|
|
|
|
*/
|
|
|
|
|
|
play::test::DummyOutputLink
|
|
|
|
|
|
getDummyConnection(uint index)
|
|
|
|
|
|
{
|
|
|
|
|
|
return dummySetup_.getModelPort (index);
|
|
|
|
|
|
}
|
2023-06-06 04:25:12 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Test support: verify the given Job is consistent with this Dispatcher.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool verify(Job const& job, ModelPort const& port, play::DataSink const& sink)
|
|
|
|
|
|
{
|
2023-06-13 03:47:42 +02:00
|
|
|
|
if (not dummySetup_.isSupported (port, sink)) return false;
|
|
|
|
|
|
|
|
|
|
|
|
TimeValue nominalTime{job.parameter.nominalTime};
|
|
|
|
|
|
size_t portIDX = resolveModelPort (port);
|
2023-06-18 03:50:48 +02:00
|
|
|
|
JobTicket& ticket = getJobTicketFor (portIDX, nominalTime);
|
2023-06-13 20:23:33 +02:00
|
|
|
|
return isnil (ticket)? MockJob::isNopJob (job)
|
2023-06-13 03:47:42 +02:00
|
|
|
|
: MockJobTicket::isAssociated (job, ticket);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
PortIdxMap
|
|
|
|
|
|
buildPortIndex()
|
|
|
|
|
|
{
|
|
|
|
|
|
PortIdxMap newIndex;
|
|
|
|
|
|
uint i{0};
|
|
|
|
|
|
for (auto it=dummySetup_.getAllModelPorts()
|
|
|
|
|
|
; bool{it}
|
|
|
|
|
|
; ++it, ++i
|
|
|
|
|
|
)
|
|
|
|
|
|
newIndex[*it] = i;
|
|
|
|
|
|
|
|
|
|
|
|
return newIndex;
|
2023-06-06 04:25:12 +02:00
|
|
|
|
}
|
2023-06-02 02:31:34 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-04-17 17:10:53 +02:00
|
|
|
|
}}} // namespace steam::engine::test
|
2023-05-24 03:38:12 +02:00
|
|
|
|
#endif /*STEAM_ENGINE_TEST_MOCK_DISPATCHER_H*/
|