/* MockSupport(Test) - verify test support for fixture and job dispatch Copyright (C) Lumiera.org 2023, Hermann Vosseler 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-support-test.cpp ** unit test \ref MockSupport_test */ #include "lib/test/run.hpp" #include "lib/error.hpp" #include "steam/engine/mock-dispatcher.hpp" #include "vault/engine/dummy-job.hpp" #include "lib/util.hpp" #include "vault/engine/nop-job-functor.hpp" #include "lib/format-cout.hpp"///////////////////////TODO //#include "steam/engine/job-planning.hpp" //#include using test::Test; using util::isSameObject; //using std::rand; namespace steam { namespace engine{ namespace test { using vault::engine::DummyJob; namespace { // test fixture... } // (End) test fixture namespace { template using cnt_ = std::integral_constant; template auto _buildSeqTuple (cnt_<0>, SEQ&&) { return std::tuple<>{}; } template auto _buildSeqTuple (cnt_, SEQ&& iter) { auto tPre = std::tie (*iter); ++iter; return std::tuple_cat (tPre, _buildSeqTuple (cnt_{}, std::forward (iter))); } } template auto seqTuple (SEQ&& iter) { return _buildSeqTuple (cnt_{}, std::forward (iter)); } /**********************************************************************//** * @test validate test support for render job planning and dispatch. * - creating and invoking mock render jobs * - a mocked JobTicket, generating mock render jobs * - configurable test setup for a mocked Segmentation datastructure * * @todo WIP-WIP-WIP 4/2023 * * @see JobPlanningSetup_test * @see Dispatcher * @see vault::engine::Job * @see steam::fixture::Segmentation */ class MockSupport_test : public Test { virtual void run (Arg) { simpleUsage(); verify_MockJob(); verify_MockJobTicket(); verify_MockSegmentation(); } /** @test simple usage example of the test helpers */ void simpleUsage() { TODO ("simple usage example"); } /** @test document and verify usage of a mock render job */ void verify_MockJob() { Time nominalTime = lib::test::randTime(); int additionalKey = rand() % 5000; Job mockJob = DummyJob::build (nominalTime, additionalKey); CHECK (mockJob.getNominalTime() == nominalTime); CHECK (not DummyJob::was_invoked (mockJob)); mockJob.triggerJob(); CHECK (DummyJob::was_invoked (mockJob)); CHECK (RealClock::wasRecently (DummyJob::invocationTime (mockJob))); CHECK (nominalTime == DummyJob::invocationNominalTime (mockJob) ); CHECK (additionalKey == DummyJob::invocationAdditionalKey(mockJob)); Time prevInvocation = DummyJob::invocationTime (mockJob); mockJob.triggerJob(); CHECK (prevInvocation < DummyJob::invocationTime (mockJob)); // invoked again, recorded new invocation time CHECK (nominalTime == DummyJob::invocationNominalTime (mockJob) ); // all other Job parameter recorded again unaltered CHECK (additionalKey == DummyJob::invocationAdditionalKey(mockJob)); } /** @test document and verify usage of a mock JobTicket for frame dispatch */ void verify_MockJobTicket() { FrameCoord coord; coord.absoluteNominalTime = lib::test::randTime(); // build a render job to do nothing.... Job nopJob = JobTicket::NOP.createJobFor(coord); CHECK (INSTANCEOF (vault::engine::NopJobFunctor, static_cast (nopJob.jobClosure))); //////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs) CHECK (nopJob.parameter.nominalTime == coord.absoluteNominalTime); InvocationInstanceID empty; ///////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs CHECK (lumiera_invokey_eq (&nopJob.parameter.invoKey, &empty)); MockJobTicket mockTicket; CHECK (mockTicket.discoverPrerequisites().empty()); Job mockJob = mockTicket.createJobFor (coord); CHECK ( mockTicket.verify_associated (mockJob)); // proof by invocation hash : is indeed backed by this JobTicket CHECK (not mockTicket.verify_associated (nopJob)); // ...while some random other job is not related TODO ("cover details of MockJobTicket"); } /** @test document and verify usage of a complete mocked Segmentation to back frame dispatch */ void verify_MockSegmentation() { FrameCoord coord; Time someTime = lib::test::randTime(); coord.absoluteNominalTime = someTime; //-----------------------------------------------------------------/// Empty default Segmentation { MockSegmentation mockSeg; CHECK (1 == mockSeg.size()); JobTicket const& ticket = mockSeg[someTime].jobTicket(); CHECK (util::isSameObject (ticket, JobTicket::NOP)); } //-----------------------------------------------------------------/// Segmentation with one default segment spanning complete timeline { MockSegmentation mockSegs{MakeRec().genNode()}; CHECK (1 == mockSegs.size()); CHECK (Time::MIN == mockSegs[someTime].start()); CHECK (Time::MAX == mockSegs[someTime].after()); JobTicket const& ticket = mockSegs[someTime].jobTicket(); CHECK (not util::isSameObject (ticket, JobTicket::NOP)); Job someJob = ticket.createJobFor(coord); // JobTicket uses, but does not check the time given in FrameCoord CHECK (someJob.parameter.nominalTime == _raw(coord.absoluteNominalTime)); CHECK (MockJobTicket::isAssociated (someJob, ticket)); // but the generated Job is linked to the Closure backed by the JobTicket CHECK (not DummyJob::was_invoked (someJob)); someJob.triggerJob(); CHECK (DummyJob::was_invoked (someJob)); CHECK (RealClock::wasRecently (DummyJob::invocationTime (someJob))); CHECK (someTime == DummyJob::invocationNominalTime (someJob)); } //-----------------------------------------------------------------/// Segmentation with a segment spanning part of the timeline > 10s { // Marker to verify the job calls back into the right segment int marker = rand() % 1000; // Build a Segmentation partitioned at 10s MockSegmentation mockSegs{MakeRec() .attrib ("start", Time{0,10} ,"mark", marker) .genNode()}; CHECK (2 == mockSegs.size()); // since only start-time was given, the SplitSplice-Algo will attach // the new Segment starting at 10s and expand towards +∞, // while the left part of the axis is marked as NOP / empty fixture::Segment const& seg1 = mockSegs[Time::ZERO]; // access anywhere < 10s fixture::Segment const& seg2 = mockSegs[Time{0,20}]; // access anywhere >= 10s CHECK ( util::isSameObject (seg1.jobTicket(), JobTicket::NOP)); CHECK (not util::isSameObject (seg2.jobTicket(), JobTicket::NOP));// this one is the active segment Job job = seg2.jobTicket().createJobFor(coord); CHECK (not MockJobTicket::isAssociated (job, seg1.jobTicket())); CHECK ( MockJobTicket::isAssociated (job, seg2.jobTicket())); CHECK (marker == job.parameter.invoKey.part.a); job.triggerJob(); CHECK (DummyJob::was_invoked (job)); CHECK (RealClock::wasRecently (DummyJob::invocationTime (job))); CHECK (marker == DummyJob::invocationAdditionalKey (job)); // DummyClosure is rigged such as to feed back the seed in `part.a` // and thus we can prove this job really belongs to the marked segment // create another job from the (empty) seg1 job = seg1.jobTicket().createJobFor (coord); InvocationInstanceID empty; /////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs CHECK (lumiera_invokey_eq (&job.parameter.invoKey, &empty)); // indicates that it's just a placeholder to mark a "NOP"-Job CHECK (seg1.jobTicket().empty()); CHECK (seg1.empty()); CHECK (not seg2.empty()); } //-----------------------------------------------------------------/// Segmentation with one delineated segment, and otherwise empty { int marker = rand() % 1000; // Build Segmentation with one fully defined segment MockSegmentation mockSegs{MakeRec() .attrib ("start", Time{0,10} ,"after", Time{0,20} ,"mark", marker) .genNode()}; CHECK (3 == mockSegs.size()); auto const& [s1,s2,s3] = seqTuple<3> (mockSegs.eachSeg()); CHECK (s1.empty()); CHECK (not s2.empty()); CHECK (s3.empty()); CHECK (isSameObject (s2, mockSegs[Time{0,10}])); CHECK (Time::MIN == s1.start()); CHECK (Time(0,10) == s1.after()); CHECK (Time(0,10) == s2.start()); CHECK (Time(0,20) == s2.after()); CHECK (Time(0,20) == s3.start()); CHECK (Time::MAX == s3.after()); Job job = s2.jobTicket().createJobFor(coord); job.triggerJob(); CHECK (marker == DummyJob::invocationAdditionalKey (job)); } TODO ("cover more details of MockSegmentation"); } }; /** Register this test class... */ LAUNCHER (MockSupport_test, "unit engine"); }}} // namespace steam::engine::test