* 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.
259 lines
7.8 KiB
C++
259 lines
7.8 KiB
C++
/*
|
||
MockDispatcher - diagnostic render job and frame dispatcher
|
||
|
||
Copyright (C)
|
||
2013, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**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.
|
||
|
||
* *****************************************************************/
|
||
|
||
/** @file mock-dispatcher.cpp
|
||
** Implementation of a dummy render job for unit tests.
|
||
** Based on using a specifically rigged DummyClosure as JobFunctor,
|
||
** where the actual Job invocation does nothing other than storing
|
||
** the invocation mark and parameters into a invocationLog_ table.
|
||
** Together with the likewise specifically rigged steam::engine::test::MockJobTicket,
|
||
** the invocation hash can be marked, which allows to prove after the invocation
|
||
** that a given Segment or JobTicket actually generated a specific Job, which was
|
||
** then invoked with specific parameters.
|
||
**
|
||
** # Usage front-end
|
||
**
|
||
** A MockJob can directly created, and then sliced down to the Job baseclass,
|
||
** since it has no additional data fields. The static functions in MockJob allow
|
||
** to verify that a given job instance was created from this setup, that it was
|
||
** invoked, and verify invocation time and extract data
|
||
*/
|
||
|
||
|
||
#include "steam/engine/mock-dispatcher.hpp"
|
||
#include "vault/gear/nop-job-functor.hpp"
|
||
#include "lib/test/test-helper.hpp"
|
||
#include "lib/time/timevalue.hpp"
|
||
#include "vault/real-clock.hpp"
|
||
#include "lib/hash-combine.hpp"
|
||
#include "lib/null-value.hpp"
|
||
#include "lib/depend.hpp"
|
||
#include "lib/util.hpp"
|
||
|
||
#include <functional>
|
||
#include <unordered_map>
|
||
|
||
|
||
namespace steam {
|
||
namespace engine{
|
||
namespace test {
|
||
|
||
namespace { // MockJob and DummyClosure implementation details...
|
||
|
||
using lib::rani;
|
||
using lib::HashVal;
|
||
using lib::NullValue;
|
||
using lib::time::TimeVar;
|
||
using std::unordered_map;
|
||
using util::access_or_default;
|
||
|
||
using vault::gear::JobParameter;
|
||
|
||
|
||
const int MAX_PARAM_A(1000); ///< random test values 0...1000
|
||
const int MAX_PARAM_B(10); ///< random test values -10...+10
|
||
|
||
|
||
/**
|
||
* MockJob objects are backed by this closure.
|
||
* Invocations of this job functor are recorded in a hashtable
|
||
* @note as of 5/2023, we use a simplistic map-based implementation,
|
||
* causing a consecutive invocation of the same job instance
|
||
* with identical JobParameter to overwrite the previous log entry.
|
||
*/
|
||
class DummyClosure
|
||
: public JobClosure
|
||
{
|
||
void
|
||
invokeJobOperation (JobParameter parameter) override
|
||
{
|
||
invocationLog_[hash_value (parameter)] = Invocation(parameter);
|
||
}
|
||
|
||
JobKind
|
||
getJobKind() const override
|
||
{
|
||
return META_JOB;
|
||
}
|
||
|
||
/**
|
||
* Generate a specifically marked invocationKey for use in unit-tests.
|
||
* @remark in the actual implementation, this function generates a distinct
|
||
* base hash to tag specific processing provided by this JobFunctor;
|
||
* the seed usually factors in the media stream format; on invocation
|
||
* the nominal frame time will additionally be hashed in. Yet for
|
||
* unit testing, we typically use this dummy JobFunctor and it is
|
||
* expedient if this hash-chaining calculation is easy predictable;
|
||
* @return a zero-initialised invocationKey, assigning seed to the lower part
|
||
*/
|
||
InvocationInstanceID
|
||
buildInstanceID (HashVal seed) const override
|
||
{
|
||
InvocationInstanceID instance;
|
||
instance.part.a = seed;
|
||
return instance;
|
||
}
|
||
|
||
size_t
|
||
hashOfInstance (InvocationInstanceID invoKey) const override
|
||
{
|
||
std::hash<size_t> hashr;
|
||
HashVal res = hashr (invoKey.frameNumber);
|
||
lib::hash::combine (res, hashr (invoKey.part.t));
|
||
return res;
|
||
}
|
||
|
||
|
||
/* === Logging/Reporting of job invocation === */
|
||
|
||
struct Invocation
|
||
{
|
||
TimeVar nominal;
|
||
TimeVar real;
|
||
int a,b;
|
||
|
||
Invocation (JobParameter param)
|
||
: nominal{TimeValue(param.nominalTime)}
|
||
, real{RealClock::now()}
|
||
, a{param.invoKey.part.a}
|
||
, b{param.invoKey.part.b}
|
||
{ }
|
||
|
||
Invocation()
|
||
: nominal{Time::ANYTIME}
|
||
, real{Time::NEVER}
|
||
, a{MAX_PARAM_A}, b{0}
|
||
{ }
|
||
};
|
||
|
||
/** recording MockJob invocations */
|
||
unordered_map<HashVal,Invocation> invocationLog_;
|
||
|
||
|
||
public:
|
||
Invocation const&
|
||
queryInvocation (JobParameter param) const
|
||
{
|
||
return access_or_default (invocationLog_, hash_value(param)
|
||
,NullValue<Invocation>::get());
|
||
}
|
||
|
||
void
|
||
clearLog()
|
||
{
|
||
invocationLog_.clear();
|
||
}
|
||
};
|
||
|
||
|
||
|
||
/** actual instance of the test dummy job functor */
|
||
DummyClosure dummyClosure;
|
||
|
||
/** access to the fallback-implementation for empty segments */
|
||
lib::Depend<vault::gear::NopJobFunctor> nopFunctor;
|
||
|
||
}// (End)Implementation details
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
Job
|
||
MockJob::build()
|
||
{
|
||
InvocationInstanceID invoKey;
|
||
invoKey.part.a = rani (MAX_PARAM_A);
|
||
invoKey.part.b = rani (2*MAX_PARAM_B - MAX_PARAM_B);
|
||
|
||
Time nominalTime = lib::test::randTime();
|
||
|
||
return Job(dummyClosure, invoKey, nominalTime);
|
||
}
|
||
|
||
|
||
Job
|
||
MockJob::build (Time nominalTime, int additionalKey)
|
||
{
|
||
InvocationInstanceID invoKey;
|
||
invoKey.part.a = additionalKey;
|
||
invoKey.part.b = rani (2*MAX_PARAM_B - MAX_PARAM_B);
|
||
|
||
return Job(dummyClosure, invoKey, nominalTime);
|
||
}
|
||
|
||
|
||
bool
|
||
MockJob::was_invoked (Job const& job)
|
||
{
|
||
REQUIRE (job.usesClosure (dummyClosure));
|
||
|
||
return Time::NEVER != dummyClosure.queryInvocation(job.parameter).real;
|
||
}
|
||
|
||
|
||
Time
|
||
MockJob::invocationTime (Job const& job)
|
||
{
|
||
REQUIRE (job.usesClosure (dummyClosure));
|
||
|
||
return dummyClosure.queryInvocation(job.parameter).real;
|
||
}
|
||
|
||
|
||
Time
|
||
MockJob::invocationNominalTime (Job const& job)
|
||
{
|
||
REQUIRE (job.usesClosure (dummyClosure));
|
||
|
||
return dummyClosure.queryInvocation(job.parameter).nominal;
|
||
}
|
||
|
||
|
||
int
|
||
MockJob::invocationAdditionalKey (Job const& job)
|
||
{
|
||
REQUIRE (job.usesClosure (dummyClosure));
|
||
|
||
return dummyClosure.queryInvocation(job.parameter).a;
|
||
}
|
||
|
||
|
||
/** @internal for collaboration with other Mock/Dummy facilities */
|
||
JobClosure&
|
||
MockJob::getFunctor()
|
||
{
|
||
return dummyClosure;
|
||
}
|
||
|
||
/** @internal likewise to support the MockDispatcher diagnostics;
|
||
* locate here since this is a dedicated translation unit
|
||
* @return `true` iff the job was defined in the typical way used by
|
||
* JobTicket to generate fill jobs for empty segments.
|
||
* @see JobTicket::JobTicket::createJobFor(nominalTime)
|
||
*/
|
||
bool
|
||
MockJob::isNopJob (Job const& job)
|
||
{
|
||
InvocationInstanceID empty; ///////////////////////////////////////////////////////////////////////TICKET #1287 : temporary workaround until we get rid of the C base structs
|
||
JobClosure& jobFunctor = static_cast<JobClosure&> (*job.jobClosure); //////////////////////////TICKET #1287 : fix actual interface down to JobFunctor (after removing C structs)
|
||
return lumiera_invokey_eq (&util::unConst(job).parameter.invoKey, &empty)
|
||
and util::isSameObject (jobFunctor, nopFunctor());
|
||
}
|
||
|
||
|
||
|
||
}}} // namespace steam::engine::test
|