/* MOCK-DISPATCHER.hpp - test scaffolding to verify render job planning and dispatch Copyright (C) 2023, Hermann Vosseler   **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. */ /** @file mock-dispatcher.hpp ** Mock data structures to support implementation testing of render job ** 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. ** - 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). 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. ** ** @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/gear/job.h" #include "vault/real-clock.hpp" #include "lib/allocator-handle.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 "lib/util.hpp" #include #include 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; using lib::ranHash; using lib::rani; using util::isnil; using util::isSameObject; using fixture::Segmentation; using vault::RealClock; using vault::gear::Job; using vault::gear::JobClosure; /** * 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 { 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(); }; /** * Mock setup for a JobTicket to generate dummy render Job 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(nominalTime) will be wired * with the MockJob functor and can thus be related back to * the test specification setup. * @see JobPlanningPipeline_test * @see DispatcherInterface_test */ class MockJobTicket : private lib::AllocatorHandle , public JobTicket { auto& allocator() { return static_cast&> (*this); } /** provide a test specification wired to MockJob */ static ExitNode defineSimpleSpec (HashVal seed =ranHash()) { return ExitNode{seed, DUMMY_JOB_RUNTIME ,ExitNodes{} ,& MockJob::getFunctor()}; } public: MockJobTicket() : JobTicket{defineSimpleSpec(), allocator()} { } MockJobTicket (HashVal seed) : JobTicket{defineSimpleSpec (seed), allocator()} { } /* ===== 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 DummyClosure 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 MockJob::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 { public: MockSegmentation() : Segmentation{} { } MockSegmentation (std::initializer_list specs) : MockSegmentation{} { for (auto& spec : specs) { auto start = spec.retrieveAttribute