2023-04-27 19:38:37 +02:00
/*
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 0213 9 , USA .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/** @file mock-support-test.cpp
* * unit test \ ref MockSupport_test
*/
# include "lib/test/run.hpp"
2023-05-23 06:40:18 +02:00
# include "lib/test/test-helper.hpp"
2023-04-27 19:38:37 +02:00
# include "steam/engine/mock-dispatcher.hpp"
2023-05-10 14:57:41 +02:00
# include "vault/engine/nop-job-functor.hpp"
2023-04-27 19:38:37 +02:00
# include "vault/engine/dummy-job.hpp"
2023-05-15 23:25:29 +02:00
# include "lib/iter-tree-explorer.hpp"
2023-05-10 13:50:19 +02:00
# include "lib/util-tuple.hpp"
2023-04-27 22:30:49 +02:00
# include "lib/util.hpp"
2023-05-23 06:40:18 +02:00
2023-04-27 19:38:37 +02:00
using test : : Test ;
2023-05-10 13:50:19 +02:00
2023-04-27 19:38:37 +02:00
namespace steam {
namespace engine {
namespace test {
2023-05-10 14:57:41 +02:00
using steam : : fixture : : Segment ;
2023-04-27 19:38:37 +02:00
using vault : : engine : : DummyJob ;
2023-05-15 23:25:29 +02:00
using lib : : singleValIterator ;
2023-05-10 14:57:41 +02:00
using util : : isSameObject ;
using util : : seqTuple ;
2023-05-10 03:59:46 +02:00
2023-04-27 19:38:37 +02:00
/**********************************************************************/ /**
* @ 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
2023-05-10 14:57:41 +02:00
run ( Arg )
2023-04-27 19:38:37 +02:00
{
simpleUsage ( ) ;
verify_MockJob ( ) ;
verify_MockJobTicket ( ) ;
verify_MockSegmentation ( ) ;
2023-05-11 22:47:56 +02:00
verify_MockPrerequisites ( ) ;
2023-04-27 19:38:37 +02:00
}
/** @test simple usage example of the test helpers */
void
simpleUsage ( )
{
2023-05-10 14:57:41 +02:00
// 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
2023-05-11 22:47:56 +02:00
JobTicket const & ticket = seg . jobTicket ( ) ; ///////////////////////////////////////////////////TICKET #1297 : will need to pass a ModelPort number here (use the first one, i.e. 0)
2023-05-10 14:57:41 +02:00
FrameCoord coord ;
coord . absoluteNominalTime = Time ( 0 , 15 ) ;
Job job = ticket . createJobFor ( coord ) ;
CHECK ( MockJobTicket : : isAssociated ( job , ticket ) ) ;
job . triggerJob ( ) ;
CHECK ( DummyJob : : was_invoked ( job ) ) ;
2023-04-27 19:38:37 +02:00
}
2023-05-10 14:57:41 +02:00
2023-04-27 19:38:37 +02:00
/** @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 ( ) ;
2023-04-30 02:18:56 +02:00
CHECK ( DummyJob : : was_invoked ( mockJob ) ) ;
2023-04-27 19:38:37 +02:00
CHECK ( RealClock : : wasRecently ( DummyJob : : invocationTime ( mockJob ) ) ) ;
CHECK ( nominalTime = = DummyJob : : invocationNominalTime ( mockJob ) ) ;
CHECK ( additionalKey = = DummyJob : : invocationAdditionalKey ( mockJob ) ) ;
2023-04-30 02:18:56 +02:00
Time prevInvocation = DummyJob : : invocationTime ( mockJob ) ;
2023-04-27 19:38:37 +02:00
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 ( )
{
2023-05-01 01:48:36 +02:00
FrameCoord coord ;
coord . absoluteNominalTime = lib : : test : : randTime ( ) ;
// build a render job to do nothing....
Job nopJob = JobTicket : : NOP . createJobFor ( coord ) ;
2023-05-01 14:07:21 +02:00
CHECK ( INSTANCEOF ( vault : : engine : : NopJobFunctor , static_cast < JobClosure * > ( nopJob . jobClosure ) ) ) ; //////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs)
2023-05-01 01:48:36 +02:00
CHECK ( nopJob . parameter . nominalTime = = coord . absoluteNominalTime ) ;
2023-05-01 14:07:21 +02:00
InvocationInstanceID empty ; ///////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
CHECK ( lumiera_invokey_eq ( & nopJob . parameter . invoKey , & empty ) ) ;
2023-05-01 01:48:36 +02:00
2023-04-30 02:18:56 +02:00
MockJobTicket mockTicket ;
CHECK ( mockTicket . discoverPrerequisites ( ) . empty ( ) ) ;
2023-05-01 01:48:36 +02:00
Job mockJob = mockTicket . createJobFor ( coord ) ;
2023-05-01 14:07:21 +02:00
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
2023-04-27 19:38:37 +02:00
}
2023-05-24 03:38:12 +02:00
2023-05-10 14:57:41 +02:00
/** @test document and verify usage of a complete mocked Segmentation
* to back frame dispatch
2023-05-24 03:38:12 +02:00
* - default constructed : empty Segmentation
* - cover the whole axis with one segment
* - partition axis and verify the association of generated jobs
* - a fully defined segment within an otherwise empty axis
* - complex partitioning ( using the » split - splice « mechanism
2023-05-10 14:57:41 +02:00
*/
2023-04-27 19:38:37 +02:00
void
verify_MockSegmentation ( )
{
2023-05-01 17:02:11 +02:00
FrameCoord coord ;
Time someTime = lib : : test : : randTime ( ) ;
coord . absoluteNominalTime = someTime ;
2023-05-24 03:38:12 +02:00
//
2023-05-10 03:59:46 +02:00
//-----------------------------------------------------------------/// Empty default Segmentation
2023-05-01 17:02:11 +02:00
{
MockSegmentation mockSeg ;
CHECK ( 1 = = mockSeg . size ( ) ) ;
JobTicket const & ticket = mockSeg [ someTime ] . jobTicket ( ) ;
CHECK ( util : : isSameObject ( ticket , JobTicket : : NOP ) ) ;
}
2023-05-10 03:59:46 +02:00
//-----------------------------------------------------------------/// Segmentation with one default segment spanning complete timeline
2023-05-01 17:02:11 +02:00
{
2023-05-05 03:46:42 +02:00
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 ( ) ;
2023-05-01 17:02:11 +02:00
CHECK ( not util : : isSameObject ( ticket , JobTicket : : NOP ) ) ;
2023-05-10 03:59:46 +02:00
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
2023-05-01 17:02:11 +02:00
CHECK ( not DummyJob : : was_invoked ( someJob ) ) ;
someJob . triggerJob ( ) ;
CHECK ( DummyJob : : was_invoked ( someJob ) ) ;
CHECK ( RealClock : : wasRecently ( DummyJob : : invocationTime ( someJob ) ) ) ;
CHECK ( someTime = = DummyJob : : invocationNominalTime ( someJob ) ) ;
}
2023-05-10 03:59:46 +02:00
//-----------------------------------------------------------------/// Segmentation with a segment spanning part of the timeline > 10s
2023-05-05 03:46:42 +02:00
{
// Marker to verify the job calls back into the right segment
int marker = rand ( ) % 1000 ;
2023-05-24 03:38:12 +02:00
//
2023-05-05 03:46:42 +02:00
// 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
2023-05-10 03:59:46 +02:00
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 ) ) ;
2023-05-05 03:46:42 +02:00
}
2023-05-10 14:57:41 +02:00
//-----------------------------------------------------------------/// 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 ) ) ;
}
2023-04-27 19:38:37 +02:00
}
2023-05-11 22:47:56 +02:00
/**
* @ 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 ) ) ;
}
2023-05-23 06:40:18 +02:00
//-----------------------------------------------------------------/// a tree of deep nested prerequisites
2023-05-15 23:25:29 +02:00
{
MockSegmentation mockSegs { MakeRec ( )
2023-05-23 06:40:18 +02:00
. attrib ( " mark " , 11 )
. scope ( MakeRec ( )
. attrib ( " mark " , 33 )
. scope ( MakeRec ( )
. attrib ( " mark " , 55 )
. genNode ( )
, MakeRec ( )
. attrib ( " mark " , 44 )
. genNode ( )
)
. genNode ( )
, MakeRec ( )
. attrib ( " mark " , 22 )
. genNode ( ) )
2023-05-15 23:25:29 +02:00
. genNode ( ) } ;
2023-05-23 01:06:22 +02:00
auto start = singleValIterator ( mockSegs [ Time : : ZERO ] . jobTicket ( ) ) ;
2023-05-15 23:25:29 +02:00
2023-05-23 06:40:18 +02:00
auto it = lib : : explore ( start )
. expand ( [ ] ( JobTicket const & ticket )
2023-05-15 23:25:29 +02:00
{
2023-05-23 01:06:22 +02:00
return ticket . getPrerequisites ( ) ;
2023-05-23 06:40:18 +02:00
} )
2023-05-15 23:25:29 +02:00
. expandAll ( )
2023-05-23 01:06:22 +02:00
. transform ( [ & ] ( JobTicket const & ticket )
2023-05-15 23:25:29 +02:00
{
2023-05-23 01:06:22 +02:00
return ticket . createJobFor ( coord ) . parameter . invoKey . part . a ;
2023-05-15 23:25:29 +02:00
} ) ;
2023-05-23 06:40:18 +02:00
CHECK ( util : : join ( it , " - " ) = = " 11-22-33-44-55 " _expect ) ;
} // Note: Prerequisites are prepended (LinkedElements)
} // thus at each level the last ones appear first
2023-04-27 19:38:37 +02:00
} ;
/** Register this test class... */
LAUNCHER ( MockSupport_test , " unit engine " ) ;
} } } // namespace steam::engine::test