2023-07-13 01:51:21 +02:00
/*
ACTIVITY - DETECTOR . hpp - test scaffolding to observe activities within the scheduler
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 activity-detector.hpp
* * Diagnostic setup to instrument and observe \ ref Activity activations .
* * The [ Scheduler ] ( \ ref scheduler . hpp ) powering the Lumiera render engine
* * is implemented in terms of Activities , which can be time - bound and depend
* * on each other . For performance reasons , these _operational atoms_ must be
* * implemented as a tightly knit network of lightweight POD records without
* * much indirection . This setup poses a challenge for unit tests and similar
* * white box testing , due to the lack of a managed platform and any further
* * means of indirection and extension . As a remedy , a set of preconfigured
* * _detector Activity records_ is provided , which drop off event log messages
* * by side effect . These detector probes can be wired in as decorators into
2023-08-15 02:23:40 +02:00
* * an otherwise valid Activity - Term , allowing to watch and verify patterns
2023-07-13 01:51:21 +02:00
* * of invocation - - which might even happen concurrently .
2023-08-15 02:23:40 +02:00
* *
* * # Usage
* *
* * An ActivityDetector instance can be created in local storage to get an arsenal
* * of probing tools and detectors , which are internally wired to record activation
* * into an lib : : test : : EventLog embedded into the ActivityDetector instance . A
* * _verification DSL_ is provided , internally relying on the building blocks and
* * the chained - search mechanism known from the EventLog . To distinguish similar
* * invocations and activations , a common _sequence number_ is maintained within
* * the ActivityDetector instance , which can be incremented explicitly . All
* * relevant events also capture the current sequence number as an attribute
* * of the generated log record .
* *
* * # # Observation tools
* * - ActivityDetector : : buildDiadnosticFun ( id ) generates a functor object with
* * _arbitrary signature , _ which records any invocation and arguments .
* * The corresponding verification matcher is # verifyInvocation ( id )
* *
* * @ todo WIP - WIP - WIP 8 / 2023 gradually gaining traction .
2023-07-13 01:51:21 +02:00
* * @ see SchedulerActivity_test
2023-08-15 02:23:40 +02:00
* * @ see EventLog_test ( demonstration of EventLog capbabilities )
2023-07-13 01:51:21 +02:00
*/
# ifndef VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H
# define VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H
# include "vault/common.hpp"
//#include "lib/test/test-helper.hpp"
2023-07-31 21:53:16 +02:00
# include "lib/test/event-log.hpp"
2023-07-13 01:51:21 +02:00
//#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"
2023-08-15 17:18:30 +02:00
# include "vault/gear/job.h"
# include "vault/gear/activity.hpp"
2023-08-15 18:52:51 +02:00
# include "vault/gear/nop-job-functor.hpp"
2023-07-13 01:51:21 +02:00
//#include "vault/real-clock.hpp"
//#include "lib/allocator-handle.hpp"
2023-08-15 18:52:51 +02:00
# include "lib/time/timevalue.hpp"
2023-07-13 01:51:21 +02:00
//#include "lib/diff/gen-node.hpp"
//#include "lib/linked-elements.hpp"
2023-07-31 21:53:16 +02:00
# include "lib/meta/variadic-helper.hpp"
2023-08-15 18:52:51 +02:00
# include "lib/meta/function.hpp"
2023-07-31 21:53:16 +02:00
# include "lib/wrapper.hpp"
2023-08-01 17:53:42 +02:00
# include "lib/format-cout.hpp"
2023-07-31 21:53:16 +02:00
# include "lib/format-util.hpp"
2023-07-13 01:51:21 +02:00
//#include "lib/itertools.hpp"
//#include "lib/depend.hpp"
2023-07-31 21:53:16 +02:00
# include "lib/util.hpp"
2023-07-13 01:51:21 +02:00
2023-07-31 21:53:16 +02:00
# include <functional>
# include <utility>
# include <string>
2023-08-15 18:52:51 +02:00
# include <deque>
2023-07-13 01:51:21 +02:00
//#include <tuple>
//#include <map>
namespace vault {
namespace gear {
namespace test {
2023-07-31 21:53:16 +02:00
using std : : string ;
2023-07-13 01:51:21 +02:00
// using std::make_tuple;
// using lib::diff::GenNode;
// using lib::diff::MakeRec;
2023-08-15 18:52:51 +02:00
using lib : : time : : TimeValue ;
2023-07-13 01:51:21 +02:00
// using lib::time::Time;
// using lib::HashVal;
2023-08-01 14:52:20 +02:00
using lib : : meta : : RebindVariadic ;
2023-07-31 21:53:16 +02:00
using util : : isnil ;
2023-08-13 20:49:30 +02:00
using std : : forward ;
using std : : move ;
2023-07-13 01:51:21 +02:00
// using util::isSameObject;
// using fixture::Segmentation;
// using vault::RealClock;
// using vault::gear::Job;
// using vault::gear::JobClosure;
2023-08-15 20:03:01 +02:00
namespace { // Diagnostic markers
2023-08-14 19:22:18 +02:00
const string MARK_INC { " IncSeq " } ;
2023-08-01 17:53:42 +02:00
const string MARK_SEQ { " Seq " } ;
2023-08-15 20:03:01 +02:00
using SIG_JobDiagnostic = void ( TimeValue , int32_t ) ;
const size_t JOB_ARG_POS_TIME = 0 ;
2023-08-17 19:37:38 +02:00
const string CTX_POST { " post " } ;
const string CTX_WORK { " work " } ;
const string CTX_DONE { " done " } ;
const string CTX_TICK { " tick " } ;
2023-08-01 17:53:42 +02:00
}
2023-08-13 20:49:30 +02:00
class ActivityDetector ;
2023-08-15 02:23:40 +02:00
/**
* @ internal ongoing evaluation and match of observed activities .
* @ remark this temporary object provides a builder API for creating
* chained verifications , similar to the usage of lib : : test : : EventLog .
* Moreover , it is convertible to ` bool ` to retrieve the verification result .
*/
2023-08-13 20:49:30 +02:00
class ActivityMatch
: private lib : : test : : EventMatch
{
using _Parent = lib : : test : : EventMatch ;
ActivityMatch ( lib : : test : : EventMatch & & matcher )
: _Parent { move ( matcher ) }
{ }
friend class ActivityDetector ;
public :
// standard copy acceptable
2023-08-15 02:23:40 +02:00
/** final evaluation of the verification query,
* usually triggered from the unit test ` CHECK ( ) ` .
* @ note failure cause is printed to STDERR .
*/
2023-08-13 20:49:30 +02:00
operator bool ( ) const { return _Parent : : operator bool ( ) ; }
// EventMatch& locate (string match);
// EventMatch& locateMatch (string regExp);
// EventMatch& locateEvent (string match);
// EventMatch& locateEvent (string classifier, string match);
// EventMatch& locateCall (string match);
//
//
// /* query builders to find a match stepping forwards */
//
// EventMatch& before (string match);
// EventMatch& beforeMatch (string regExp);
// EventMatch& beforeEvent (string match);
// EventMatch& beforeEvent (string classifier, string match);
2023-08-13 23:42:04 +02:00
ActivityMatch & beforeInvocation ( string match ) { return delegate ( & EventMatch : : beforeCall , move ( match ) ) ; }
2023-08-13 20:49:30 +02:00
//
//
// /* query builders to find a match stepping backwards */
//
// EventMatch& after (string match);
// EventMatch& afterMatch (string regExp);
// EventMatch& afterEvent (string match);
// EventMatch& afterEvent (string classifier, string match);
2023-08-15 02:23:40 +02:00
ActivityMatch & afterInvocation ( string match ) { return delegate ( & EventMatch : : afterCall , move ( match ) ) ; }
2023-08-13 20:49:30 +02:00
2023-08-13 23:42:04 +02:00
/** qualifier: additionally match the function arguments */
template < typename . . . ARGS >
ActivityMatch &
arg ( ARGS const & . . . args )
{
return delegate ( & EventMatch : : arg < ARGS . . . > , args . . . ) ;
}
/** qualifier: additionally require the indicated sequence number */
ActivityMatch &
seq ( uint seqNr )
{
_Parent : : attrib ( MARK_SEQ , util : : toString ( seqNr ) ) ;
return * this ;
}
2023-08-15 02:23:40 +02:00
/** special query to match an increment of the sequence number */
2023-08-14 19:22:18 +02:00
ActivityMatch &
beforeSeqIncrement ( uint seqNr )
{
_Parent : : beforeEvent ( MARK_INC , util : : toString ( seqNr ) ) ;
return * this ;
}
2023-08-15 20:03:01 +02:00
ActivityMatch &
afterSeqIncrement ( uint seqNr )
{
_Parent : : afterEvent ( MARK_INC , util : : toString ( seqNr ) ) ;
return * this ;
}
/** qualifier: additionally match the nominal time argument of JobFunctor invocation */
ActivityMatch &
nominalTime ( TimeValue const & time )
{
return delegate ( & EventMatch : : argPos < TimeValue const & > , size_t ( JOB_ARG_POS_TIME ) , time ) ;
}
2023-08-14 19:22:18 +02:00
2023-08-15 02:23:40 +02:00
2023-08-13 20:49:30 +02:00
private :
2023-08-15 02:23:40 +02:00
/** @internal helper to delegate to the inherited matcher building blocks
* @ note since ActivityMatch can only be created by ActivityDetector ,
* we can be sure the EventMatch reference returned from these calls
* is actually a reference to ` * this ` , and can thus be downcasted .
* */
2023-08-13 20:49:30 +02:00
template < typename . . . ARGS >
ActivityMatch &
delegate ( _Parent & ( _Parent : : * fun ) ( ARGS . . . ) , ARGS & & . . . args )
{
return static_cast < ActivityMatch & > (
( this - > * fun ) ( forward < ARGS > ( args ) . . . ) ) ;
}
} ;
2023-07-13 01:51:21 +02:00
/**
* Diagnostic context to record and evaluate activations within the Scheduler .
2023-08-15 02:23:40 +02:00
* The provided tools and detectors are wired back internally , such as to record
* any observations into an lib : : test : : EventLog instance . Thus , after performing
* rigged functionality , the expected activities and their order can be verified .
* @ see ActivityDetector_test
* @ todo WIP - WIP - WIP 8 / 23 gradually building the verification tools needed . . .
2023-07-13 01:51:21 +02:00
*/
class ActivityDetector
: util : : NonCopyable
{
2023-07-31 21:53:16 +02:00
using EventLog = lib : : test : : EventLog ;
EventLog eventLog_ ;
2023-08-15 02:23:40 +02:00
uint invocationSeq_ ;
2023-07-31 21:53:16 +02:00
/**
* A Mock functor , logging all invocations into the EventLog
*/
template < typename RET , typename . . . ARGS >
class DiagnosticFun
{
using RetVal = lib : : wrapper : : ItemWrapper < RET > ;
string id_ ;
EventLog * log_ ;
2023-08-15 02:23:40 +02:00
uint const * seqNr_ ;
2023-07-31 21:53:16 +02:00
RetVal retVal_ ;
public :
2023-08-15 02:23:40 +02:00
DiagnosticFun ( string id , EventLog & masterLog , uint const & invocationSeqNr )
2023-07-31 21:53:16 +02:00
: id_ { id }
, log_ { & masterLog }
2023-08-15 02:23:40 +02:00
, seqNr_ { & invocationSeqNr }
2023-07-31 21:53:16 +02:00
, retVal_ { }
{ }
/** prepare a response value to return from the mock invocation */
2023-08-01 17:53:42 +02:00
template < typename VAL >
2023-07-31 21:53:16 +02:00
DiagnosticFun & &
2023-08-01 17:53:42 +02:00
returning ( VAL & & riggedResponse )
2023-07-31 21:53:16 +02:00
{
2023-08-01 17:53:42 +02:00
retVal_ = std : : forward < VAL > ( riggedResponse ) ;
2023-07-31 21:53:16 +02:00
return std : : move ( * this ) ;
}
/** mock function call operator: logs all invocations */
RET
2023-08-17 19:37:38 +02:00
operator ( ) ( ARGS . . . args )
2023-07-31 21:53:16 +02:00
{
2023-08-13 23:42:04 +02:00
log_ - > call ( log_ - > getID ( ) , id_ , args . . . )
2023-08-15 02:23:40 +02:00
. addAttrib ( MARK_SEQ , util : : toString ( * seqNr_ ) ) ;
2023-07-31 21:53:16 +02:00
return * retVal_ ;
}
} ;
2023-07-13 01:51:21 +02:00
2023-08-15 18:52:51 +02:00
/** @internal type rebinding helper */
template < typename SIG >
struct _DiagnosticFun
{
using Ret = typename lib : : meta : : _Fun < SIG > : : Ret ;
using Args = typename lib : : meta : : _Fun < SIG > : : Args ;
using ArgsX = typename lib : : meta : : StripNullType < Args > : : Seq ; ////////////////////////////////////TICKET #987 : make lib::meta::Types<TYPES...> variadic
using SigTypes = typename lib : : meta : : Prepend < Ret , ArgsX > : : Seq ;
using Type = typename RebindVariadic < DiagnosticFun , SigTypes > : : Type ;
} ;
/**
* A Mocked job operation to detect any actual invocation
*/
class MockJobFunctor
: public NopJobFunctor
{
2023-08-15 20:03:01 +02:00
using MockOp = typename _DiagnosticFun < SIG_JobDiagnostic > : : Type ;
2023-08-15 18:52:51 +02:00
MockOp mockOperation_ ;
/** rigged diagnostic implementation of job invocation
* @ note only data relevant for diagnostics is explicitly unpacked
*/
void
invokeJobOperation ( JobParameter param ) override
{
mockOperation_ ( TimeValue { param . nominalTime } , param . invoKey . part . a ) ;
}
public :
MockJobFunctor ( MockOp mockedJobOperation )
: mockOperation_ { move ( mockedJobOperation ) }
{ }
} ;
/* ===== Maintain throw-away mock instances ===== */
std : : deque < MockJobFunctor > mockOps_ { } ;
2023-07-13 01:51:21 +02:00
2023-07-31 21:53:16 +02:00
public :
2023-08-01 17:53:42 +02:00
ActivityDetector ( string id = " " )
2023-07-31 21:53:16 +02:00
: eventLog_ { " ActivityDetector " + ( isnil ( id ) ? string { } : " ( " + id + " ) " ) }
2023-08-01 17:53:42 +02:00
, invocationSeq_ { 0 }
2023-07-13 01:51:21 +02:00
{ }
2023-07-31 21:53:16 +02:00
operator string ( ) const
{
return util : : join ( eventLog_ ) ;
}
2023-08-15 02:23:40 +02:00
string
showLog ( ) const
{
return " \n ____Event-Log___________________________ \n "
+ util : : join ( eventLog_ , " \n " )
+ " \n ────╼━━━━━━━━╾────────────────────────── "
;
}
2023-07-31 21:53:16 +02:00
void
clear ( string newID )
{
if ( isnil ( newID ) )
eventLog_ . clear ( ) ;
else
eventLog_ . clear ( newID ) ;
}
2023-08-15 02:23:40 +02:00
/** increment the internal invocation sequence number */
2023-08-01 17:53:42 +02:00
uint
operator + + ( )
{
+ + invocationSeq_ ;
2023-08-15 02:23:40 +02:00
eventLog_ . event ( MARK_INC , util : : toString ( invocationSeq_ ) ) ;
2023-08-01 17:53:42 +02:00
return invocationSeq_ ;
}
uint
currSeq ( ) const
{
return invocationSeq_ ;
}
2023-07-31 21:53:16 +02:00
/**
* Generic testing helper : build a λ - mock , logging all invocations
* @ tparam SIG signature of the functor to be generated
* @ param id human readable ID , to designate invocations in the log
* @ return a function object with signature # SIG
*/
template < typename SIG >
auto
buildDiagnosticFun ( string id )
{
2023-08-15 18:52:51 +02:00
using Functor = typename _DiagnosticFun < SIG > : : Type ;
2023-08-13 23:42:04 +02:00
return Functor { id , eventLog_ , invocationSeq_ } ;
2023-07-31 21:53:16 +02:00
}
2023-08-15 18:52:51 +02:00
JobClosure & ///////////////////////////////////////////////////////////////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs)
2023-08-15 17:18:30 +02:00
buildMockJobFunctor ( string id )
{
2023-08-15 18:52:51 +02:00
return mockOps_ . emplace_back (
2023-08-15 20:03:01 +02:00
buildDiagnosticFun < SIG_JobDiagnostic > ( id ) ) ;
2023-08-15 17:18:30 +02:00
}
2023-08-13 20:49:30 +02:00
2023-08-01 17:53:42 +02:00
2023-08-17 19:37:38 +02:00
struct FakeExecutionCtx ;
using SIG_post = activity : : Proc ( Activity & , FakeExecutionCtx & , Time ) ;
using SIG_work = void ( Time , size_t ) ;
using SIG_done = void ( Time , size_t ) ;
using SIG_tick = activity : : Proc ( Time ) ;
/**
* Mock setup of the execution context for Activity activation .
* The instance # executionCtx is wired back with the # eventLog_
* and allows thus to detect and verify all callbacks from the Activities .
* @ note the return value of the # post and # tick functions can be changed
* to another fixed response by calling DiagnosticFun : : returning
*/
struct FakeExecutionCtx
{
_DiagnosticFun < SIG_post > : : Type post ;
_DiagnosticFun < SIG_work > : : Type work ;
_DiagnosticFun < SIG_done > : : Type done ;
_DiagnosticFun < SIG_tick > : : Type tick ;
FakeExecutionCtx ( ActivityDetector & adi )
: post { adi . buildDiagnosticFun < SIG_post > ( CTX_POST ) . returning ( activity : : PASS ) }
, work { adi . buildDiagnosticFun < SIG_work > ( CTX_WORK ) }
, done { adi . buildDiagnosticFun < SIG_done > ( CTX_DONE ) }
, tick { adi . buildDiagnosticFun < SIG_tick > ( CTX_TICK ) . returning ( activity : : PASS ) }
{ }
} ;
FakeExecutionCtx executionCtx { * this } ;
2023-08-13 23:42:04 +02:00
ActivityMatch
verifyInvocation ( string fun )
2023-08-01 17:53:42 +02:00
{
2023-08-13 23:42:04 +02:00
return ActivityMatch { move ( eventLog_ . verifyCall ( fun ) ) } ;
2023-08-13 20:49:30 +02:00
}
2023-08-14 19:22:18 +02:00
ActivityMatch
ensureNoInvocation ( string fun )
{
return ActivityMatch { move ( eventLog_ . ensureNot ( fun ) . locateCall ( fun ) ) } ;
}
ActivityMatch
verifySeqIncrement ( uint seqNr )
{
return ActivityMatch { move ( eventLog_ . verifyEvent ( MARK_INC , util : : toString ( seqNr ) ) ) } ;
}
2023-07-13 01:51:21 +02:00
private :
} ;
} } } // namespace vault::gear::test
# endif /*VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H*/