2023-07-13 01:51:21 +02:00
/*
ACTIVITY - DETECTOR . hpp - test scaffolding to observe activities within the scheduler
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright ( C )
2023 , Hermann Vosseler < Ichthyostega @ web . de >
2023-07-13 01:51:21 +02:00
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
* * 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 .
2023-07-13 01:51:21 +02:00
*/
/** @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-09-03 01:50:50 +02:00
* * of invocation .
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 )
2023-09-03 01:50:50 +02:00
* * - ActivityDetector : : buildMockJobFunctor ( id ) a JobFunctor implementation
* * suitably rigged to record invocations and arguments
* * - ActivityDetector : : buildActivationProbe a debugging Activity to record activation
* * - ActivityDetector : : insertActivationTap hooks this Activation - Probe before an
* * existing Activity - connection , so that passing on the activation can be detected
* * - ActivityDetector : : watchGate rig a ` GATE ` activity by prepending and appending
* * an Activation - Probe , so that both incoming and outgoing activations can be traced
* * - ActivityDetector : : executionCtx test setup of the execution environment abstraction
* * for performing chains of Activities ; it provides the expected λ - functions as
* * instances of ActivityDetctor : : DiagnosticFun , so that any invocation is recorded
2023-08-15 02:23:40 +02:00
* *
2023-07-13 01:51:21 +02:00
* * @ see SchedulerActivity_test
2023-09-03 01:50:50 +02:00
* * @ see ActivityDetector_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"
2023-08-29 02:18:07 +02:00
# include "lib/test/test-helper.hpp"
2023-07-31 21:53:16 +02:00
# include "lib/test/event-log.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"
# include "lib/time/timevalue.hpp"
2025-01-01 22:02:08 +01:00
# include "lib/meta/variadic-rebind.hpp"
2025-02-16 23:16:46 +01:00
# include "lib/meta/typeseq-util.hpp"
2023-08-15 18:52:51 +02:00
# include "lib/meta/function.hpp"
2025-05-30 23:21:33 +02:00
# include "lib/item-wrapper.hpp"
2023-07-31 21:53:16 +02:00
# include "lib/format-util.hpp"
2024-11-13 02:23:23 +01:00
# include "lib/random.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
namespace vault {
namespace gear {
namespace test {
2023-07-31 21:53:16 +02:00
using std : : string ;
2023-08-31 23:55:42 +02:00
using std : : function ;
2023-08-15 18:52:51 +02:00
using lib : : time : : TimeValue ;
2023-08-18 04:00:21 +02:00
using lib : : time : : Time ;
2023-08-20 02:39:57 +02:00
using lib : : time : : FSecs ;
using lib : : time : : Offset ;
2023-08-01 14:52:20 +02:00
using lib : : meta : : RebindVariadic ;
Scheduler-test: rework handling of notifications in the Activity-Language
While the recent refactoring...
206c67cc
...was a step into the right direction, it pushed too hard,
overlooking the requirement to protect the scheduler contents
and thus all of the Activity-chains against concurrent modification.
Moreover, the recent solution still seems not quite orthogonal.
Thus the handling of notifications was thoroughly reworked:
- the explicit "double-dispatch" was removed, since actual usage
of the language indicates that we only need notifications to
Gate (and Hook), but not to any other conceivable Activity.
- thus it seems unnecessary to turn "notification" into some kind
of secondary work mode. Rather, it is folded as special case
into the regular dispatch.
This leads to new processing rules:
- a POST goes into λ-post (obviously... that's its meaning)
- a NOTIFY now passes its *target* into λ-post
- λ-post invokes ''dispatch''
- and **dispatching a Gate now implies to notify the Gate**
This greatly simplifies the »state machine« in the Activity-Language,
but also incurs some limitations (which seems adequate, since it is
now clear that we do not ''schedule'' or ''dispatch'' arbitrary
Activities — rather we'll do this only with POST and NOTIFY,
and all further processing happens by passing activation
along the chain, without involving the Scheduler)
2023-12-16 23:47:50 +01:00
using util : : unConst ;
2023-07-31 21:53:16 +02:00
using util : : isnil ;
2024-11-13 02:23:23 +01:00
using lib : : rani ;
2023-08-13 20:49:30 +02:00
using std : : forward ;
using std : : move ;
2023-07-13 01:51:21 +02:00
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
2023-08-18 04:00:21 +02:00
using SIG_JobDiagnostic = void ( Time , int32_t ) ;
2023-08-15 20:03:01 +02:00
const size_t JOB_ARG_POS_TIME = 0 ;
2023-08-17 19:37:38 +02:00
2023-08-18 04:00:21 +02:00
const string CTX_POST { " CTX-post " } ;
const string CTX_WORK { " CTX-work " } ;
const string CTX_DONE { " CTX-done " } ;
const string CTX_TICK { " CTX-tick " } ;
2023-08-20 02:39:57 +02:00
2023-09-01 19:23:27 +02:00
Time SCHED_TIME_MARKER { 555 , 5 } ; ///< marker value for "current scheduler time" used in tests
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 ( ) ; }
2023-09-03 01:50:50 +02:00
/* query builder(s) to find a match stepping forwards */
2023-08-13 23:42:04 +02:00
ActivityMatch & beforeInvocation ( string match ) { return delegate ( & EventMatch : : beforeCall , move ( match ) ) ; }
2023-09-03 01:50:50 +02:00
// more here...
/* query builders to find a match stepping backwards */
2023-08-15 02:23:40 +02:00
ActivityMatch & afterInvocation ( string match ) { return delegate ( & EventMatch : : afterCall , move ( match ) ) ; }
2023-09-03 01:50:50 +02:00
// more here...
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 &
2023-08-19 17:48:38 +02:00
timeArg ( Time const & time )
2023-08-15 20:03:01 +02:00
{
2023-08-18 04:00:21 +02:00
return delegate ( & EventMatch : : argPos < Time const & > , size_t ( JOB_ARG_POS_TIME ) , time ) ;
2023-08-15 20:03:01 +02:00
}
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
2023-09-03 01:50:50 +02:00
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
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 > ;
2023-09-01 21:59:25 +02:00
using ImplFun = std : : function < RET ( ARGS . . . ) > ;
2023-07-31 21:53:16 +02:00
string id_ ;
EventLog * log_ ;
2023-08-15 02:23:40 +02:00
uint const * seqNr_ ;
2023-09-01 21:59:25 +02:00
ImplFun implFun_ ;
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-09-01 21:59:25 +02:00
, implFun_ { }
2023-07-31 21:53:16 +02:00
, retVal_ { }
2023-09-01 21:59:25 +02:00
{
retVal_ . defaultInit ( ) ;
}
2023-07-31 21:53:16 +02:00
/** 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 ) ;
}
2023-09-01 21:59:25 +02:00
/** use the given λ to provide (optional) implementation logic */
template < class FUN >
DiagnosticFun & &
implementedAs ( FUN & & customImpl )
{
implFun_ = std : : forward < FUN > ( customImpl ) ;
return std : : move ( * this ) ;
}
2023-08-18 19:37:44 +02:00
// default copyable
2023-07-31 21:53:16 +02:00
/** mock function call operator: logs all invocations */
RET
2023-10-18 23:02:29 +02:00
operator ( ) ( ARGS . . . args ) const
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-09-01 21:59:25 +02:00
return implFun_ ? implFun_ ( std : : forward < ARGS > ( args ) . . . )
: * retVal_ ;
2023-07-31 21:53:16 +02:00
}
2023-08-18 19:37:44 +02:00
operator string ( ) const
{
return log_ - > getID ( ) + " . " + id_ ;
}
2023-07-31 21:53:16 +02:00
} ;
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 ;
2025-06-02 17:46:40 +02:00
using ArgsX = typename lib : : meta : : StripNil < Args > : : Seq ; ////////////////////////////////////TICKET #987 : make lib::meta::Types<TYPES...> variadic
2023-08-15 18:52:51 +02:00
using SigTypes = typename lib : : meta : : Prepend < Ret , ArgsX > : : Seq ;
using Type = typename RebindVariadic < DiagnosticFun , SigTypes > : : Type ;
} ;
2023-08-18 19:37:44 +02:00
using Logger = _DiagnosticFun < void ( string ) > : : Type ;
2023-08-15 18:52:51 +02:00
/**
* 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
{
2023-08-18 04:00:21 +02:00
mockOperation_ ( Time { TimeValue { param . nominalTime } } , param . invoKey . part . a ) ;
2023-08-15 18:52:51 +02:00
}
2023-08-19 19:06:44 +02:00
string diagnostic ( ) const override
{
return " JobFun- " + string { mockOperation_ } ;
}
2023-12-08 03:16:57 +01:00
JobKind
getJobKind ( ) const
{
return TEST_JOB ;
}
2023-08-19 19:06:44 +02:00
2023-08-15 18:52:51 +02:00
public :
MockJobFunctor ( MockOp mockedJobOperation )
: mockOperation_ { move ( mockedJobOperation ) }
{ }
} ;
2023-08-18 19:37:44 +02:00
/**
* A rigged CALLBACK - Activity to watch passing of activations .
*/
class ActivityProbe
: public Activity
2023-10-25 22:40:13 +02:00
, public activity : : Hook
2023-08-18 19:37:44 +02:00
{
Logger log_ ;
2023-10-25 22:40:13 +02:00
TimeVar invoked_ { Time : : ANYTIME } ;
Scheduler-test: rework handling of notifications in the Activity-Language
While the recent refactoring...
206c67cc
...was a step into the right direction, it pushed too hard,
overlooking the requirement to protect the scheduler contents
and thus all of the Activity-chains against concurrent modification.
Moreover, the recent solution still seems not quite orthogonal.
Thus the handling of notifications was thoroughly reworked:
- the explicit "double-dispatch" was removed, since actual usage
of the language indicates that we only need notifications to
Gate (and Hook), but not to any other conceivable Activity.
- thus it seems unnecessary to turn "notification" into some kind
of secondary work mode. Rather, it is folded as special case
into the regular dispatch.
This leads to new processing rules:
- a POST goes into λ-post (obviously... that's its meaning)
- a NOTIFY now passes its *target* into λ-post
- λ-post invokes ''dispatch''
- and **dispatching a Gate now implies to notify the Gate**
This greatly simplifies the »state machine« in the Activity-Language,
but also incurs some limitations (which seems adequate, since it is
now clear that we do not ''schedule'' or ''dispatch'' arbitrary
Activities — rather we'll do this only with POST and NOTIFY,
and all further processing happens by passing activation
along the chain, without involving the Scheduler)
2023-12-16 23:47:50 +01:00
Activity *
target ( )
{
return reinterpret_cast < Activity * > ( data_ . callback . arg ) ;
}
Activity const *
target ( ) const
{
return unConst ( this ) - > target ( ) ;
}
2023-08-18 19:37:44 +02:00
activity : : Proc
activation ( Activity & thisHook
, Time now
, void * executionCtx ) override
Scheduler-test: rework handling of notifications in the Activity-Language
While the recent refactoring...
206c67cc
...was a step into the right direction, it pushed too hard,
overlooking the requirement to protect the scheduler contents
and thus all of the Activity-chains against concurrent modification.
Moreover, the recent solution still seems not quite orthogonal.
Thus the handling of notifications was thoroughly reworked:
- the explicit "double-dispatch" was removed, since actual usage
of the language indicates that we only need notifications to
Gate (and Hook), but not to any other conceivable Activity.
- thus it seems unnecessary to turn "notification" into some kind
of secondary work mode. Rather, it is folded as special case
into the regular dispatch.
This leads to new processing rules:
- a POST goes into λ-post (obviously... that's its meaning)
- a NOTIFY now passes its *target* into λ-post
- λ-post invokes ''dispatch''
- and **dispatching a Gate now implies to notify the Gate**
This greatly simplifies the »state machine« in the Activity-Language,
but also incurs some limitations (which seems adequate, since it is
now clear that we do not ''schedule'' or ''dispatch'' arbitrary
Activities — rather we'll do this only with POST and NOTIFY,
and all further processing happens by passing activation
along the chain, without involving the Scheduler)
2023-12-16 23:47:50 +01:00
{
REQUIRE ( thisHook . is ( Activity : : HOOK ) ) ;
invoked_ = now ;
if ( not target ( ) )
{ // no adapted target; just record this activation
log_ ( util : : toString ( now ) + " ⧐ " ) ;
return activity : : PASS ;
}
else
{ // forward activation to the adapted target Activity
auto ctx = * static_cast < FakeExecutionCtx * > ( executionCtx ) ;
log_ ( util : : toString ( now ) + " ⧐ " + util : : toString ( * target ( ) ) ) ;
return target ( ) - > activate ( now , ctx ) ;
}
}
2023-08-18 19:37:44 +02:00
2023-08-21 17:32:52 +02:00
activity : : Proc
notify ( Activity & thisHook
, Time now
, void * executionCtx ) override
Scheduler-test: rework handling of notifications in the Activity-Language
While the recent refactoring...
206c67cc
...was a step into the right direction, it pushed too hard,
overlooking the requirement to protect the scheduler contents
and thus all of the Activity-chains against concurrent modification.
Moreover, the recent solution still seems not quite orthogonal.
Thus the handling of notifications was thoroughly reworked:
- the explicit "double-dispatch" was removed, since actual usage
of the language indicates that we only need notifications to
Gate (and Hook), but not to any other conceivable Activity.
- thus it seems unnecessary to turn "notification" into some kind
of secondary work mode. Rather, it is folded as special case
into the regular dispatch.
This leads to new processing rules:
- a POST goes into λ-post (obviously... that's its meaning)
- a NOTIFY now passes its *target* into λ-post
- λ-post invokes ''dispatch''
- and **dispatching a Gate now implies to notify the Gate**
This greatly simplifies the »state machine« in the Activity-Language,
but also incurs some limitations (which seems adequate, since it is
now clear that we do not ''schedule'' or ''dispatch'' arbitrary
Activities — rather we'll do this only with POST and NOTIFY,
and all further processing happens by passing activation
along the chain, without involving the Scheduler)
2023-12-16 23:47:50 +01:00
{
REQUIRE ( thisHook . is ( Activity : : HOOK ) ) ;
invoked_ = now ;
if ( not target ( ) )
{ // no adapted target; just record this notification
log_ ( util : : toString ( now ) + " --notify-↯• " ) ;
return activity : : PASS ;
}
else
{ // forward notification-dispatch to the adapted target Activity
auto ctx = * static_cast < FakeExecutionCtx * > ( executionCtx ) ;
log_ ( util : : toString ( now ) + " --notify-↯> " + util : : toString ( * target ( ) ) ) ;
return target ( ) - > dispatch ( now , ctx ) ;
}
}
2023-08-21 17:32:52 +02:00
Scheduler-test: rework handling of notifications in the Activity-Language
While the recent refactoring...
206c67cc
...was a step into the right direction, it pushed too hard,
overlooking the requirement to protect the scheduler contents
and thus all of the Activity-chains against concurrent modification.
Moreover, the recent solution still seems not quite orthogonal.
Thus the handling of notifications was thoroughly reworked:
- the explicit "double-dispatch" was removed, since actual usage
of the language indicates that we only need notifications to
Gate (and Hook), but not to any other conceivable Activity.
- thus it seems unnecessary to turn "notification" into some kind
of secondary work mode. Rather, it is folded as special case
into the regular dispatch.
This leads to new processing rules:
- a POST goes into λ-post (obviously... that's its meaning)
- a NOTIFY now passes its *target* into λ-post
- λ-post invokes ''dispatch''
- and **dispatching a Gate now implies to notify the Gate**
This greatly simplifies the »state machine« in the Activity-Language,
but also incurs some limitations (which seems adequate, since it is
now clear that we do not ''schedule'' or ''dispatch'' arbitrary
Activities — rather we'll do this only with POST and NOTIFY,
and all further processing happens by passing activation
along the chain, without involving the Scheduler)
2023-12-16 23:47:50 +01:00
Time
getDeadline ( ) const override
{
if ( target ( ) and target ( ) - > is ( Activity : : GATE ) )
return target ( ) - > data_ . condition . getDeadline ( ) ;
else
return Time : : NEVER ;
}
std : : string
diagnostic ( ) const override
{
return " Probe( " + string { log_ } + " ) " ;
}
2023-08-18 19:37:44 +02:00
public :
ActivityProbe ( string id , EventLog & masterLog , uint const & invocationSeqNr )
: Activity { * this , 0 }
, log_ { id , masterLog , invocationSeqNr }
{ }
Scheduler-test: rework handling of notifications in the Activity-Language
While the recent refactoring...
206c67cc
...was a step into the right direction, it pushed too hard,
overlooking the requirement to protect the scheduler contents
and thus all of the Activity-chains against concurrent modification.
Moreover, the recent solution still seems not quite orthogonal.
Thus the handling of notifications was thoroughly reworked:
- the explicit "double-dispatch" was removed, since actual usage
of the language indicates that we only need notifications to
Gate (and Hook), but not to any other conceivable Activity.
- thus it seems unnecessary to turn "notification" into some kind
of secondary work mode. Rather, it is folded as special case
into the regular dispatch.
This leads to new processing rules:
- a POST goes into λ-post (obviously... that's its meaning)
- a NOTIFY now passes its *target* into λ-post
- λ-post invokes ''dispatch''
- and **dispatching a Gate now implies to notify the Gate**
This greatly simplifies the »state machine« in the Activity-Language,
but also incurs some limitations (which seems adequate, since it is
now clear that we do not ''schedule'' or ''dispatch'' arbitrary
Activities — rather we'll do this only with POST and NOTIFY,
and all further processing happens by passing activation
along the chain, without involving the Scheduler)
2023-12-16 23:47:50 +01:00
2023-08-19 19:06:44 +02:00
ActivityProbe ( Activity const & subject , string id , EventLog & masterLog , uint const & invocationSeqNr )
: Activity { * this , reinterpret_cast < size_t > ( & subject ) }
, log_ { id , masterLog , invocationSeqNr }
{
next = subject . next ;
}
2023-08-18 19:37:44 +02:00
operator string ( ) const
{
return diagnostic ( ) ;
}
2023-10-25 22:40:13 +02:00
static Time
lastInvoked ( Activity const * act )
{
if ( act and act - > verb_ = = HOOK )
{
Scheduler-test: rework handling of notifications in the Activity-Language
While the recent refactoring...
206c67cc
...was a step into the right direction, it pushed too hard,
overlooking the requirement to protect the scheduler contents
and thus all of the Activity-chains against concurrent modification.
Moreover, the recent solution still seems not quite orthogonal.
Thus the handling of notifications was thoroughly reworked:
- the explicit "double-dispatch" was removed, since actual usage
of the language indicates that we only need notifications to
Gate (and Hook), but not to any other conceivable Activity.
- thus it seems unnecessary to turn "notification" into some kind
of secondary work mode. Rather, it is folded as special case
into the regular dispatch.
This leads to new processing rules:
- a POST goes into λ-post (obviously... that's its meaning)
- a NOTIFY now passes its *target* into λ-post
- λ-post invokes ''dispatch''
- and **dispatching a Gate now implies to notify the Gate**
This greatly simplifies the »state machine« in the Activity-Language,
but also incurs some limitations (which seems adequate, since it is
now clear that we do not ''schedule'' or ''dispatch'' arbitrary
Activities — rather we'll do this only with POST and NOTIFY,
and all further processing happens by passing activation
along the chain, without involving the Scheduler)
2023-12-16 23:47:50 +01:00
ActivityProbe * probe = dynamic_cast < ActivityProbe * > ( act - > data_ . callback . hook ) ;
2023-10-25 22:40:13 +02:00
if ( probe )
return probe - > invoked_ ;
}
return Time : : NEVER ;
}
2023-08-18 19:37:44 +02:00
} ;
2023-08-15 18:52:51 +02:00
/* ===== Maintain throw-away mock instances ===== */
std : : deque < MockJobFunctor > mockOps_ { } ;
2023-08-18 19:37:44 +02:00
std : : deque < ActivityProbe > mockActs_ { } ;
2023-08-15 18:52:51 +02:00
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
2023-08-22 20:13:13 +02:00
incrementSeq ( )
2023-08-01 17:53:42 +02:00
{
+ + 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-29 02:18:07 +02:00
Job
buildMockJob ( string id = " "
, Time nominal = lib : : test : : randTime ( )
2024-11-13 02:23:23 +01:00
, size_t extra = rani ( ) )
2023-08-29 02:18:07 +02:00
{
InvocationInstanceID invoKey ;
invoKey . part . a = extra ;
invoKey . part . t = _raw ( nominal ) ;
return Job { buildMockJobFunctor ( isnil ( id ) ? " mockJob- " + util : : toString ( nominal ) : id )
, invoKey
, nominal } ;
}
2023-08-19 19:06:44 +02:00
/** build a rigged HOOK-Activity to record each invocation */
2023-08-18 19:37:44 +02:00
Activity &
buildActivationProbe ( string id )
{
return mockActs_ . emplace_back ( id , eventLog_ , invocationSeq_ ) ;
2023-08-19 19:06:44 +02:00
}
/** build ActivationProbe to record each activation before passing it to the subject */
Activity &
buildActivationTap ( Activity const & subject , string id = " " )
{
return mockActs_ . emplace_back ( subject
2024-11-14 22:10:43 +01:00
, isnil ( id ) ? " tap- " + subject . showVerb ( ) + util : : showAdr ( subject )
2023-08-19 19:06:44 +02:00
: id
, eventLog_
, invocationSeq_ ) ;
2023-08-18 19:37:44 +02:00
}
2023-08-01 17:53:42 +02:00
2023-08-19 23:59:18 +02:00
/** build ActivationProbe to record each activation before passing it to the subject */
Activity &
insertActivationTap ( Activity * & wiring , string id = " " )
{
wiring = wiring ? & buildActivationTap ( * wiring , id )
2024-11-14 22:10:43 +01:00
: & buildActivationProbe ( isnil ( id ) ? " tail- " + util : : showAdr ( & wiring ) : id ) ;
2023-08-19 23:59:18 +02:00
return * wiring ;
2023-08-20 01:35:14 +02:00
}
Activity &
2023-08-20 02:39:57 +02:00
buildGateWatcher ( Activity & gate , string id = " " )
2023-08-20 01:35:14 +02:00
{
2024-11-14 22:10:43 +01:00
insertActivationTap ( gate . next , " after- " + ( isnil ( id ) ? gate . showVerb ( ) + util : : showAdr ( gate ) : id ) ) ;
2023-08-20 02:39:57 +02:00
return buildActivationTap ( gate , id ) ;
2023-08-20 01:35:14 +02:00
}
Activity &
2023-08-20 02:39:57 +02:00
watchGate ( Activity * & wiring , string id = " " )
2023-08-20 01:35:14 +02:00
{
2023-08-20 02:39:57 +02:00
wiring = wiring ? & buildGateWatcher ( * wiring , id )
2024-11-14 22:10:43 +01:00
: & buildActivationProbe ( isnil ( id ) ? " tail- " + util : : showAdr ( & wiring ) : id ) ;
2023-08-20 01:35:14 +02:00
return * wiring ;
2023-08-19 23:59:18 +02:00
}
2023-08-17 19:37:38 +02:00
2023-10-25 22:40:13 +02:00
Time invokeTime ( Activity const * hook ) { return ActivityProbe : : lastInvoked ( hook ) ; }
bool wasInvoked ( Activity const * hook ) { return invokeTime ( hook ) . isRegular ( ) ; }
Time invokeTime ( Activity const & hook ) { return invokeTime ( & hook ) ; }
bool wasInvoked ( Activity const & hook ) { return wasInvoked ( & hook ) ; }
2023-08-17 19:37:38 +02:00
struct FakeExecutionCtx ;
2023-12-13 19:42:38 +01:00
using SIG_post = activity : : Proc ( Time , Time , Activity * , FakeExecutionCtx & ) ;
2023-08-17 19:37:38 +02:00
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 ;
2023-09-01 17:18:32 +02:00
function < Time ( ) > getSchedTime = [ this ] { return SCHED_TIME_MARKER ; } ;
2023-08-20 02:39:57 +02:00
2023-08-31 23:55:42 +02:00
FakeExecutionCtx ( ActivityDetector & detector )
: post { detector . buildDiagnosticFun < SIG_post > ( CTX_POST ) . returning ( activity : : PASS ) }
, work { detector . buildDiagnosticFun < SIG_work > ( CTX_WORK ) }
, done { detector . buildDiagnosticFun < SIG_done > ( CTX_DONE ) }
, tick { detector . buildDiagnosticFun < SIG_tick > ( CTX_TICK ) . returning ( activity : : PASS ) }
2023-08-17 19:37:38 +02:00
{ }
2023-08-18 04:00:21 +02:00
operator string ( ) const { return " ≺test::CTX≻ " ; }
2023-08-17 19:37:38 +02:00
} ;
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*/