Lumiera 0.pre.04
»edit your freedom«
Loading...
Searching...
No Matches
mock-support-test.cpp
Go to the documentation of this file.
1/*
2 MockSupport(Test) - verify test support for fixture and job dispatch
3
4 Copyright (C)
5 2023, Hermann Vosseler <Ichthyostega@web.de>
6
7  **Lumiera** is free software; you can redistribute it and/or modify it
8  under the terms of the GNU General Public License as published by the
9  Free Software Foundation; either version 2 of the License, or (at your
10  option) any later version. See the file COPYING for further details.
11
12* *****************************************************************/
13
19#include "lib/test/run.hpp"
23#include "lib/iter-explorer.hpp"
24#include "lib/util-tuple.hpp"
25#include "lib/util.hpp"
26
27
28using test::Test;
29
30
31namespace steam {
32namespace engine{
33namespace test {
34
38 using util::seqTuple;
39
40
41
42 /**********************************************************************/
53 class MockSupport_test : public Test
54 {
55
56 virtual void
68
69
71 void
73 {
74 // Build a simple Segment at [10s ... 20s[
75 MockSegmentation mockSegs{MakeRec()
76 .attrib ("start", Time{0,10}
77 ,"after", Time{0,20})
78 .genNode()};
79 CHECK (3 == mockSegs.size());
80 fixture::Segment const& seg = mockSegs[Time{0,15}]; // access anywhere 10s <= t < 20s
81
82 JobTicket& ticket = seg.jobTicket(0);
83
84 Job job = ticket.createJobFor (Time{0,15});
85 CHECK (MockJobTicket::isAssociated (job, ticket));
86
87 job.triggerJob();
88 CHECK (MockJob::was_invoked (job));
89 }
90
91
92
93
95 void
97 {
98 Time nominalTime = lib::test::randTime();
99 int additionalKey = rani(5000);
100 MockJob mockJob{nominalTime, additionalKey};
101 CHECK (mockJob.getNominalTime() == nominalTime);
102 CHECK (not MockJob::was_invoked (mockJob));
103
104 mockJob.triggerJob();
105 CHECK (MockJob::was_invoked (mockJob));
107 CHECK (nominalTime == MockJob::invocationNominalTime (mockJob) );
108 CHECK (additionalKey == MockJob::invocationAdditionalKey(mockJob));
109
110 Time prevInvocation = MockJob::invocationTime (mockJob);
111 mockJob.triggerJob();
112 CHECK (prevInvocation < MockJob::invocationTime (mockJob)); // invoked again, recorded new invocation time
113 CHECK (nominalTime == MockJob::invocationNominalTime (mockJob) ); // all other Job parameter recorded again unaltered
114 CHECK (additionalKey == MockJob::invocationAdditionalKey(mockJob));
115 }
116
117
119 void
121 {
122 auto frameTime = lib::test::randTime();
123
124 // build a render job to do nothing....
125 Job nopJob = JobTicket::NOP.createJobFor (frameTime);
126 CHECK (INSTANCEOF (vault::gear::NopJobFunctor, static_cast<JobClosure*> (nopJob.jobClosure)));
127 CHECK (nopJob.parameter.nominalTime == frameTime);
129 CHECK (lumiera_invokey_eq (&nopJob.parameter.invoKey, &empty));
130 CHECK (MockJob::isNopJob(nopJob)); // this diagnostic helper checks the same conditions as done here explicitly
131
132 MockJobTicket mockTicket;
133 CHECK (not mockTicket.empty());
134 Job mockJob = mockTicket.createJobFor (frameTime);
135 CHECK ( mockTicket.verify_associated (mockJob)); // proof by invocation hash : is indeed backed by this JobTicket
136 CHECK (not mockTicket.verify_associated (nopJob)); // ...while some random other job is not related
137 CHECK (not MockJob::isNopJob(mockJob));
138 }
139
140
141
150 void
152 {
153 Time someTime = lib::test::randTime();
154 //
155 //-----------------------------------------------------------------/// Empty default Segmentation
156 {
157 MockSegmentation mockSeg;
158 CHECK (1 == mockSeg.size());
159 JobTicket const& ticket = mockSeg[someTime].jobTicket(0); // just probe JobTicket generated for Model-Port-Nr.0
160 CHECK (util::isSameObject (ticket, JobTicket::NOP));
161 }
162 //-----------------------------------------------------------------/// Segmentation with one default segment spanning complete timeline
163 {
164 MockSegmentation mockSegs{MakeRec().genNode()};
165 CHECK (1 == mockSegs.size());
166 CHECK (Time::MIN == mockSegs[someTime].start());
167 CHECK (Time::MAX == mockSegs[someTime].after());
168 JobTicket& ticket = mockSegs[someTime].jobTicket(0);
169 CHECK (not util::isSameObject (ticket, JobTicket::NOP));
170
171 Job someJob = ticket.createJobFor(someTime); // JobTicket uses, but does not check the time given
172 CHECK (someJob.parameter.nominalTime == someTime);
173 CHECK (MockJobTicket::isAssociated (someJob, ticket)); // but the generated Job is linked to the Closure backed by the JobTicket
174 CHECK (not MockJob::was_invoked (someJob));
175
176 someJob.triggerJob();
177 CHECK (MockJob::was_invoked (someJob));
179 CHECK (someTime == MockJob::invocationNominalTime (someJob));
180 }
181 //-----------------------------------------------------------------/// Segmentation with a segment spanning part of the timeline > 10s
182 {
183 // Marker to verify the job calls back into the right segment
184 int marker = rani(1000);
185 //
186 // Build a Segmentation partitioned at 10s
187 MockSegmentation mockSegs{MakeRec()
188 .attrib ("start", Time{0,10}
189 ,"mark", marker)
190 .genNode()};
191 CHECK (2 == mockSegs.size());
192 // since only start-time was given, the SplitSplice-Algo will attach
193 // the new Segment starting at 10s and expand towards +∞,
194 // while the left part of the axis is marked as NOP / empty
195 fixture::Segment const& seg1 = mockSegs[Time::ZERO]; // access anywhere < 10s
196 fixture::Segment const& seg2 = mockSegs[Time{0,20}]; // access anywhere >= 10s
198 CHECK (not util::isSameObject (seg2.jobTicket(0),JobTicket::NOP));// this one is the active segment
199
200 Job job = seg2.jobTicket(0).createJobFor(someTime);
201 CHECK (not MockJobTicket::isAssociated (job, seg1.jobTicket(0)));
202 CHECK ( MockJobTicket::isAssociated (job, seg2.jobTicket(0)));
203 CHECK (marker == job.parameter.invoKey.part.a);
204
205 job.triggerJob();
206 CHECK (MockJob::was_invoked (job));
208 CHECK (marker == MockJob::invocationAdditionalKey (job)); // DummyClosure is rigged such as to feed back the seed in `part.a`
209 // and thus we can prove this job really belongs to the marked segment
210 // create another job from the (empty) seg1
211 job = seg1.jobTicket(0).createJobFor (someTime);
213 CHECK (lumiera_invokey_eq (&job.parameter.invoKey, &empty)); // indicates that it's just a placeholder to mark a "NOP"-Job
214 CHECK (seg1.jobTicket(0).empty());
215 CHECK (seg1.empty());
216 CHECK (not seg2.empty());
217 }
218 //-----------------------------------------------------------------/// Segmentation with one delineated segment, and otherwise empty
219 {
220 int marker = rani(1000);
221 // Build Segmentation with one fully defined segment
222 MockSegmentation mockSegs{MakeRec()
223 .attrib ("start", Time{0,10}
224 ,"after", Time{0,20}
225 ,"mark", marker)
226 .genNode()};
227 CHECK (3 == mockSegs.size());
228 auto const& [s1,s2,s3] = seqTuple<3> (mockSegs.eachSeg());
229 CHECK (s1.empty());
230 CHECK (not s2.empty());
231 CHECK (s3.empty());
232 CHECK (isSameObject (s2, mockSegs[Time{0,10}]));
233 CHECK (Time::MIN == s1.start());
234 CHECK (Time(0,10) == s1.after());
235 CHECK (Time(0,10) == s2.start());
236 CHECK (Time(0,20) == s2.after());
237 CHECK (Time(0,20) == s3.start());
238 CHECK (Time::MAX == s3.after());
239
240 Job job = s2.jobTicket(0).createJobFor(someTime);
241 job.triggerJob();
242 CHECK (marker == MockJob::invocationAdditionalKey (job));
243 }
244 //-----------------------------------------------------------------/// Segmentation with several segments built in specific order
245 {
246 // Build Segmentation by partitioning in several steps
247 MockSegmentation mockSegs{MakeRec()
248 .attrib ("start", Time{0,20} // note: inverted segment definition is rectified automatically
249 ,"after", Time{0,10}
250 ,"mark", 1)
251 .genNode()
252 ,MakeRec()
253 .attrib ("after", Time::ZERO
254 ,"mark", 2)
255 .genNode()
256 ,MakeRec()
257 .attrib ("start", Time{FSecs{-5}}
258 ,"mark", 3)
259 .genNode()};
260
261 CHECK (5 == mockSegs.size());
262 auto const& [s1,s2,s3,s4,s5] = seqTuple<5> (mockSegs.eachSeg());
263 CHECK (not s1.empty());
264 CHECK (not s2.empty());
265 CHECK ( s3.empty());
266 CHECK (not s4.empty());
267 CHECK ( s5.empty());
268 CHECK (Time::MIN == s1.start()); // the second added segment has covered the whole negative axis
269 CHECK (-Time(0,5) == s1.after()); // ..up to the partitioning point -5
270 CHECK (-Time(0,5) == s2.start()); // ...while the rest was taken up by the third added segment
271 CHECK (Time(0, 0) == s2.after());
272 CHECK (Time(0, 0) == s3.start()); // an empty gap remains between [0 ... 10[
273 CHECK (Time(0,10) == s3.after());
274 CHECK (Time(0,10) == s4.start()); // here is the first added segment
275 CHECK (Time(0,20) == s4.after());
276 CHECK (Time(0,20) == s5.start()); // and the remaining part of the positive axis is empty
277 CHECK (Time::MAX == s5.after());
278
279 auto probeKey = [&](Segment const& segment)
280 {
281 if (segment.empty()) return 0;
282
283 Job job = segment.jobTicket(0).createJobFor(someTime);
284 job.triggerJob();
285 CHECK (MockJob::was_invoked (job));
287
289 };
290 CHECK (2 == probeKey(s1)); // verify all generated jobs are wired back to the correct segment
291 CHECK (3 == probeKey(s2));
292 CHECK (0 == probeKey(s3));
293 CHECK (1 == probeKey(s4));
294 CHECK (0 == probeKey(s5));
295 }
296 }
297
298
299
305 void
307 {
308 Time someTime = lib::test::randTime();
309 //-----------------------------------------------------------------/// one Segment with one additional prerequisite
310 {
311 MockSegmentation mockSegs{MakeRec()
312 .attrib("mark", 11)
313 .scope(MakeRec()
314 .attrib("mark",23)
315 .genNode())
316 .genNode()};
317 CHECK (1 == mockSegs.size());
318 JobTicket& ticket = mockSegs[Time::ZERO].jobTicket(0); // Model-PortNr.0
319 auto prereq = ticket.getPrerequisites();
320 CHECK (not isnil (prereq));
321
322 JobTicket& preTicket = *prereq;
323 ++prereq;
324 CHECK (isnil (prereq));
325
326 Job job1 = preTicket.createJobFor (someTime);
327 Job job2 = ticket.createJobFor (someTime);
328
329 job1.triggerJob();
330 job2.triggerJob();
331 CHECK (23 == MockJob::invocationAdditionalKey (job1));
332 CHECK (11 == MockJob::invocationAdditionalKey (job2));
333 }
334 //-----------------------------------------------------------------/// a tree of deep nested prerequisites
335 {
336 MockSegmentation mockSegs{MakeRec()
337 .attrib("mark", 11)
338 .scope(MakeRec()
339 .attrib("mark",33)
340 .scope(MakeRec()
341 .attrib("mark",55)
342 .genNode()
343 ,MakeRec()
344 .attrib("mark",44)
345 .genNode()
346 )
347 .genNode()
348 ,MakeRec()
349 .attrib("mark",22)
350 .genNode())
351 .genNode()};
352
353 auto start = singleValIterator (mockSegs[Time::ZERO].jobTicket(0));
354
355 auto it = lib::explore(start)
356 .expand ([](JobTicket& ticket)
357 {
358 return ticket.getPrerequisites();
359 })
360 .expandAll()
361 .transform ([&](JobTicket& ticket)
362 {
363 return ticket.createJobFor(someTime).parameter.invoKey.part.a;
364 });
365
366
367 CHECK (util::join(it,"-") == "11-22-33-44-55"_expect);
368 } // Note: Prerequisites are prepended (LinkedElements)
369 } // thus at each level the last ones appear first
370
371
372
382 void
384 {
385 {
386 MockDispatcher dispatcher;
387 // automatically generates some fake connection points...
388 auto [port0,sink0] = dispatcher.getDummyConnection(0);
389 auto [port1,sink1] = dispatcher.getDummyConnection(1);
390 CHECK (port0 != port1);
391 CHECK (sink0 != sink1);
392 CHECK (port0.isValid());
393 CHECK (port1.isValid());
394 CHECK (sink0.isValid());
395 CHECK (sink1.isValid());
396 CHECK (not ModelPort().isValid());
397 CHECK (not DataSink().isValid());
398
399 CHECK (0 == dispatcher.resolveModelPort(port0));
400 CHECK (1 == dispatcher.resolveModelPort(port1));
401
402 Time frameTime{0,30};
403 size_t modelPortIDX = 0;
404 Job job0 = dispatcher.createJobFor (modelPortIDX, frameTime);
405 modelPortIDX = 1;
406 Job job1 = dispatcher.createJobFor (modelPortIDX, frameTime);
407 CHECK (dispatcher.verify(job0, port0, sink0));
408 CHECK (dispatcher.verify(job1, port1, sink1));
409 }
410 //-----------------------------------------------------------------/// can define multiple Segments
411 {
412 MockDispatcher dispatcher{MakeRec()
413 .attrib("mark", 11)
414 .genNode()
415 ,MakeRec()
416 .attrib("mark", 22)
417 .attrib("start", Time{0,10}) // second segment covers 10s … +Time::MAX
418 .genNode()};
419
420 size_t modelPortIDX = 1;
421 Job job0 = dispatcher.createJobFor (modelPortIDX, Time{0,5});
422 Job job1 = dispatcher.createJobFor (modelPortIDX, Time{0,25});
423
424 CHECK (11 == job0.parameter.invoKey.part.a);
425 CHECK (22 == job1.parameter.invoKey.part.a);
426 }
427 }
428 };
429
430
432 LAUNCHER (MockSupport_test, "unit engine");
433
434
435
436}}} // namespace steam::engine::test
Mutator && scope(X const &initialiser, ARGS &&...args)
Definition record.hpp:578
Mutator && attrib(string const &key, X &&initialiser, ARGS &&...args)
Definition record.hpp:569
Lumiera's internal time value datatype.
static const Time MIN
static const Time ZERO
static const Time MAX
Job createJobFor(size_t portIDX, TimeValue nominalTime)
Convenience shortcut for tests: JobTicket ⟼ Job.
execution plan for pulling a specific exit node.
Job createJobFor(Time nominalTime)
Core operation: build a concrete render job based on this blueprint.
static JobTicket NOP
special »do nothing« JobTicket marker
auto getPrerequisites()
Core operation: iterate over the prerequisites, required to carry out a render operation based on thi...
A mocked frame Dispatcher setup without any backing model.
bool verify(Job const &job, ModelPort const &port, play::DataSink const &sink)
Test support: verify the given Job is consistent with this Dispatcher.
size_t resolveModelPort(ModelPort modelPort) override
translate a generic ModelPort spec into the specific index number applicable at the Timeline referred...
play::test::DummyOutputLink getDummyConnection(uint index)
The faked builder/playback setup provides some preconfigured ModelPort and corresponding DataSink han...
Mock setup for a JobTicket to generate dummy render Job invocations.
bool verify_associated(Job const &) const
verify the given job instance was actually generated from this JobTicket.
static bool isAssociated(Job const &, JobTicket const &)
convenience shortcut to perform this test on arbitrary JobTicket and Job instances.
Mock setup for a render Job with NO action but built-in diagnostics.
static bool was_invoked(Job const &job)
static Time invocationNominalTime(Job const &job)
static int invocationAdditionalKey(Job const &job)
static Time invocationTime(Job const &job)
static bool isNopJob(Job const &)
Mock setup for a complete Segmentation to emulate the structure of the actual fixture,...
For the purpose of building and rendering, the fixture (for each timeline) is partitioned such that e...
Definition segment.hpp:60
engine::JobTicket & jobTicket(size_t portNr) const
Access the JobTicket for this segment and the given portNr.
Definition segment.hpp:122
Handle designating a point within the model, where actually output data can be pulled.
denotes an opened connection ready to receive media data for output.
Abstract Base Class for all testcases.
Definition run.hpp:54
void seedRand()
draw a new random seed from a common nucleus, and re-seed the default-Gen.
Definition suite.cpp:211
static bool wasRecently(Time event)
Interface of the closure for frame rendering jobs.
Definition job.h:244
Individual frame rendering task, forwarding to a closure.
Definition job.h:276
void triggerJob() const
Definition job.cpp:70
Stub/Test implementation of the JobFunctor interface for a render job to do nothing at all
Building tree expanding and backtracking evaluations within hierarchical scopes.
int lumiera_invokey_eq(void *l, void *r)
Definition job.cpp:154
opaque ID attached to each individual job invocation.
Definition job.h:105
Mock data structures to support implementation testing of render job planning and frame dispatch.
auto explore(IT &&srcSeq)
start building a IterExplorer by suitably wrapping the given iterable source.
auto singleValIterator(VAL &&something)
Build a SingleValIter: convenience free function shortcut, to pick up just any value and wrap it as L...
Steam-Layer implementation namespace root.
Test runner and basic definitions for tests.
bool isSameObject(A const &a, B const &b)
compare plain object identity, based directly on the referee's memory identities.
Definition util.hpp:421
auto seqTuple(SEQ &&iter)
Unpack an iterator to build a fixed-size std::tuple of references.
string join(COLL &&coll, string const &delim=", ")
enumerate a collection's contents, separated by delimiter.
Generic implementation of a JobFunctor to perform no calculations.
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
Definition run.hpp:116
A collection of frequently used helper functions to support unit testing.
Some small helpers and convenience shortcuts to simplify working with tuples and sequences (given by ...
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
#define INSTANCEOF(CLASS, EXPR)
shortcut for subclass test, intended for assertions only.
Definition util.hpp:514