...which uncovers further deeply nested problems, especially when referring to non-copyable types. Thus need to construct a common type that can be used both to refer to the source elements and the expanded elements, and use this common type as result type and also attempt to produce better diagnostic messages on type mismatch....
481 lines
23 KiB
C++
481 lines
23 KiB
C++
/*
|
|
MockSupport(Test) - verify test support for fixture and job 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-support-test.cpp
|
|
** unit test \ref MockSupport_test
|
|
*/
|
|
|
|
|
|
#include "lib/test/run.hpp"
|
|
#include "steam/engine/mock-dispatcher.hpp"
|
|
#include "vault/engine/nop-job-functor.hpp"
|
|
#include "vault/engine/dummy-job.hpp"
|
|
#include "lib/iter-tree-explorer.hpp"
|
|
#include "lib/util-tuple.hpp"
|
|
#include "lib/util.hpp"
|
|
#include "lib/format-cout.hpp"
|
|
#include "lib/test/test-helper.hpp"
|
|
#include "lib/meta/duck-detector.hpp"///////////////TODO WIP
|
|
|
|
using test::Test;
|
|
|
|
|
|
///////////////////////////////////////////////////////TODO WIP for investigation
|
|
namespace lib {
|
|
namespace iter_explorer {
|
|
template<class RES>
|
|
using DecoTraits = _DecoratorTraits<RES>;
|
|
template<class SRC, class RES>
|
|
using ExpoTraits = _ExpanderTraits<SRC,RES>;
|
|
}}
|
|
///////////////////////////////////////////////////////TODO WIP for investigation
|
|
namespace steam {
|
|
namespace engine{
|
|
namespace test {
|
|
|
|
using steam::fixture::Segment;
|
|
using vault::engine::DummyJob;
|
|
using lib::singleValIterator;
|
|
using util::isSameObject;
|
|
using util::seqTuple;
|
|
|
|
///////////////////////////////////////////////////////TODO WIP for investigation
|
|
template<class U>
|
|
struct ReBind
|
|
{
|
|
using type = typename U::type;
|
|
};
|
|
// template<typename X, typename SEL = void>
|
|
// struct has_TypeResult : std::false_type { };
|
|
//
|
|
// template<typename X>
|
|
// struct has_TypeResult<X, typename ReBind<X>::type> : std::true_type { };
|
|
//
|
|
// template<typename X>
|
|
// struct has_TypeResult<X, typename X::Type> : std::true_type { };
|
|
|
|
// using lib::meta::Yes_t;
|
|
// using lib::meta::No_t;
|
|
//
|
|
// template<typename TY>
|
|
// class HasNested_type
|
|
// {
|
|
// template<class X>
|
|
// static Yes_t check(typename X::type *);
|
|
// template<class X>
|
|
// static Yes_t check(typename X::Type *);
|
|
// template<class>
|
|
// static No_t check(...);
|
|
//
|
|
// public:
|
|
// static const bool value = (sizeof(Yes_t)==sizeof(check<TY>(0)));
|
|
// };
|
|
//
|
|
// template<typename X>
|
|
// struct has_TypeResult : std::bool_constant<HasNested_type<X>::value> { };
|
|
///////////////////////////////////////////////////////TODO WIP for investigation
|
|
|
|
|
|
/**********************************************************************//**
|
|
* @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
|
|
* @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();
|
|
verify_MockPrerequisites();
|
|
}
|
|
|
|
|
|
/** @test simple usage example of the test helpers */
|
|
void
|
|
simpleUsage()
|
|
{
|
|
// Build a simple Segment at [10s ... 20s[
|
|
MockSegmentation mockSegs{MakeRec()
|
|
.attrib ("start", Time{0,10}
|
|
,"after", Time{0,20})
|
|
.genNode()};
|
|
CHECK (3 == mockSegs.size());
|
|
fixture::Segment const& seg = mockSegs[Time{0,15}]; // access anywhere 10s <= t < 20s
|
|
|
|
JobTicket const& ticket = seg.jobTicket(); ///////////////////////////////////////////////////TICKET #1297 : will need to pass a ModelPort number here (use the first one, i.e. 0)
|
|
|
|
FrameCoord coord;
|
|
coord.absoluteNominalTime = Time(0,15);
|
|
Job job = ticket.createJobFor(coord);
|
|
CHECK (MockJobTicket::isAssociated (job, ticket));
|
|
|
|
job.triggerJob();
|
|
CHECK (DummyJob::was_invoked (job));
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @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<JobClosure*> (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
|
|
}
|
|
|
|
|
|
/** @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));
|
|
}
|
|
//-----------------------------------------------------------------/// Segmentation with several segments built in specific order
|
|
{
|
|
// Build Segmentation by partitioning in several steps
|
|
MockSegmentation mockSegs{MakeRec()
|
|
.attrib ("start", Time{0,20} // note: inverted segment definition is rectified automatically
|
|
,"after", Time{0,10}
|
|
,"mark", 1)
|
|
.genNode()
|
|
,MakeRec()
|
|
.attrib ("after", Time::ZERO
|
|
,"mark", 2)
|
|
.genNode()
|
|
,MakeRec()
|
|
.attrib ("start", Time{FSecs{-5}}
|
|
,"mark", 3)
|
|
.genNode()};
|
|
|
|
CHECK (5 == mockSegs.size());
|
|
auto const& [s1,s2,s3,s4,s5] = seqTuple<5> (mockSegs.eachSeg());
|
|
CHECK (not s1.empty());
|
|
CHECK (not s2.empty());
|
|
CHECK ( s3.empty());
|
|
CHECK (not s4.empty());
|
|
CHECK ( s5.empty());
|
|
CHECK (Time::MIN == s1.start()); // the second added segment has covered the whole negative axis
|
|
CHECK (-Time(0,5) == s1.after()); // ..up to the partitioning point -5
|
|
CHECK (-Time(0,5) == s2.start()); // ...while the rest was taken up by the third added segment
|
|
CHECK (Time(0, 0) == s2.after());
|
|
CHECK (Time(0, 0) == s3.start()); // an empty gap remains between [0 ... 10[
|
|
CHECK (Time(0,10) == s3.after());
|
|
CHECK (Time(0,10) == s4.start()); // here is the first added segment
|
|
CHECK (Time(0,20) == s4.after());
|
|
CHECK (Time(0,20) == s5.start()); // and the remaining part of the positive axis is empty
|
|
CHECK (Time::MAX == s5.after());
|
|
|
|
auto probeKey = [&](Segment const& segment)
|
|
{
|
|
if (segment.empty()) return 0;
|
|
|
|
Job job = segment.jobTicket().createJobFor(coord);
|
|
job.triggerJob();
|
|
CHECK (DummyJob::was_invoked (job));
|
|
CHECK (RealClock::wasRecently (DummyJob::invocationTime (job)));
|
|
|
|
return DummyJob::invocationAdditionalKey (job);
|
|
};
|
|
CHECK (2 == probeKey(s1)); // verify all generated jobs are wired back to the correct segment
|
|
CHECK (3 == probeKey(s2));
|
|
CHECK (0 == probeKey(s3));
|
|
CHECK (1 == probeKey(s4));
|
|
CHECK (0 == probeKey(s5));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @test build a Segment with additional prerequisites,
|
|
* resulting in additional JobTickets to explore and
|
|
* additional prerequisite Jobs to build for each frame.
|
|
*/
|
|
void
|
|
verify_MockPrerequisites()
|
|
{
|
|
FrameCoord coord;
|
|
//-----------------------------------------------------------------/// one Segment with one additional prerequisite
|
|
{
|
|
MockSegmentation mockSegs{MakeRec()
|
|
.attrib("mark", 11)
|
|
.scope(MakeRec()
|
|
.attrib("mark",23)
|
|
.genNode())
|
|
.genNode()};
|
|
CHECK (1 == mockSegs.size());
|
|
JobTicket const& ticket = mockSegs[Time::ZERO].jobTicket();
|
|
auto prereq = ticket.getPrerequisites();
|
|
CHECK (not isnil (prereq));
|
|
|
|
JobTicket const& preTicket = *prereq;
|
|
++prereq;
|
|
CHECK (isnil (prereq));
|
|
|
|
Job job1 = preTicket.createJobFor (coord);
|
|
Job job2 = ticket.createJobFor (coord);
|
|
|
|
job1.triggerJob();
|
|
job2.triggerJob();
|
|
CHECK (23 == DummyJob::invocationAdditionalKey (job1));
|
|
CHECK (11 == DummyJob::invocationAdditionalKey (job2));
|
|
}
|
|
//-----------------------------------------------------------------/// deep nested prerequisite
|
|
{
|
|
MockSegmentation mockSegs{MakeRec()
|
|
.attrib("mark", 11)
|
|
.scope(MakeRec()
|
|
.attrib("mark",23)
|
|
.genNode())
|
|
.genNode()};
|
|
|
|
using RTick = std::reference_wrapper<JobTicket>;
|
|
auto start = singleValIterator (mockSegs[Time::ZERO].jobTicket());
|
|
|
|
using SrC = lib::iter_explorer::BaseAdapter<lib::SingleValIter<engine::JobTicket const&> >;
|
|
|
|
auto bunny = [](JobTicket const& ticket)
|
|
{
|
|
return ticket.getPrerequisites();
|
|
// return lib::transformIterator(ticket.getPrerequisites()
|
|
// ,[](JobTicket const& preq) -> JobTicket*
|
|
// { return unConst(&preq); }
|
|
// );
|
|
};
|
|
using ExIt = decltype(bunny(std::declval<JobTicket const&>()));
|
|
|
|
using Funny = std::function<ExIt(JobTicket const&)>;
|
|
Funny funny = bunny;
|
|
|
|
using ExpandedChildren = typename lib::iter_explorer::_FunTraits<Funny,SrC>::Res;
|
|
|
|
|
|
using ResIter = typename lib::iter_explorer::DecoTraits<ExpandedChildren>::SrcIter;
|
|
// lib::test::TypeDebugger<ResIter> buggy;
|
|
using ResIterVal = typename ResIter::value_type;
|
|
using SrcIterVal = typename SrC::value_type;
|
|
// lib::test::TypeDebugger<ResIterVal> buggy;
|
|
// lib::test::TypeDebugger<ExIt> bugggy;
|
|
|
|
using FunResTrait = lib::iter_explorer::_FunTraits<Funny,ResIter>;
|
|
using FunArg = typename FunResTrait::Arg;
|
|
using ArgAdaptRes = typename FunResTrait::ArgAdapter<ResIter>;
|
|
static_assert(std::is_convertible<typename ResIter::reference, FunArg>());
|
|
// lib::test::TypeDebugger<decltype(ArgAdaptRes::wrap(bunny))> buggy;
|
|
|
|
// using ResCore = iter_explorer::Expander<SRC, ExpandedChildren>;
|
|
using ResCore = lib::iter_explorer::Expander<SrC, ExpandedChildren>;
|
|
|
|
// using ExResIter = typename lib::iter_explorer::DecoTraits<ResCore>::SrcIter;
|
|
// static_assert(lib::meta::can_IterForEach<ResCore>::value);
|
|
// static_assert(lib::meta::can_STL_ForEach<ResCore>::value);
|
|
struct Murks
|
|
{
|
|
using type = void;
|
|
};
|
|
struct Gurks : Murks { };
|
|
static_assert(lib::meta::has_TypeResult<Gurks>());
|
|
using Wootz = std::common_type<JobTicket&, JobTicket const&>;
|
|
using Wauzz = typename Wootz::type;
|
|
// lib::test::TypeDebugger<Wauzz> bully;
|
|
|
|
static_assert(lib::meta::has_TypeResult<std::common_type<JobTicket*, void*>>());
|
|
// static_assert(HasNested_type<Gurks>::value);
|
|
// static_assert(HasNested_type<Wootz>::value);
|
|
// static_assert(has_TypeResult<Wootz>());
|
|
|
|
using ExiTrait = lib::iter_explorer::ExpoTraits<SrC, ExpandedChildren>;
|
|
using WrapIter = typename lib::iter_explorer::DecoTraits<ResCore>::SrcIter;
|
|
// lib::test::TypeDebugger<typename lib::meta::ValueTypeBinding<WrapIter>::reference> bully;
|
|
// static_assert(std::is_const_v<JobTicket const&>);
|
|
// static_assert(std::is_const_v<JobTicket const>);
|
|
// static_assert(std::is_const_v<JobTicket&&>);
|
|
// lib::test::TypeDebugger<std::common_type_t<JobTicket const, JobTicket const>> bully;
|
|
// lib::test::TypeDebugger<typename ExiTrait::CommonType> bully;
|
|
// lib::test::TypeDebugger<typename ExiTrait::reference> bully;
|
|
// lib::test::TypeDebugger<ResCore::reference> bully;
|
|
|
|
|
|
auto it = lib::explore(start)
|
|
// .transform ([](RTick t) -> JobTicket const&
|
|
// {
|
|
// return t.get();
|
|
// })
|
|
.expand (funny)
|
|
.expandAll()
|
|
.transform ([&](JobTicket const& ticket)
|
|
{
|
|
return ticket.createJobFor(coord).parameter.invoKey.part.a;
|
|
});
|
|
cout << util::join(it,"-") <<endl;
|
|
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1294
|
|
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1294
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/** Register this test class... */
|
|
LAUNCHER (MockSupport_test, "unit engine");
|
|
|
|
|
|
|
|
}}} // namespace steam::engine::test
|