/* NodeDevel(Test) - Render Node development and test support Copyright (C) 2024, Hermann Vosseler   **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 node-devel-test.cpp ** Unit test \ref NodeDevel_test verifies helpers for testing of render nodes. */ #include "lib/test/run.hpp" #include "lib/hash-combine.hpp" #include "lib/test/test-helper.hpp" #include "steam/engine/node-builder.hpp" #include "steam/engine/test-rand-ontology.hpp" #include "steam/engine/diagnostic-buffer-provider.hpp" #include "lib/test/diagnostic-output.hpp"/////////////////TODO #include "lib/iter-zip.hpp" #include "lib/random.hpp" //#include "lib/util.hpp" #include using lib::zip; using lib::izip; using std::vector; using std::make_tuple; using lib::test::showType; namespace steam { namespace engine{ namespace test { namespace { /** uninitialised local storage that can be passed * as working buffer and accessed as TestFrame */ struct Buffer : util::NonCopyable { alignas(TestFrame) std::byte storage[sizeof(TestFrame)]; operator TestFrame* () { return std::launder (reinterpret_cast (&storage)); } TestFrame* operator->() { return std::launder (reinterpret_cast (&storage)); } TestFrame& operator* () { return * std::launder (reinterpret_cast (&storage)); } TestFrame& buildData (uint seq=0, uint family=0) { return * new(&storage) TestFrame{seq,family}; } }; } /***************************************************************//** * @test verify support for developing Render Node functionality. */ class NodeDevel_test : public Test { virtual void run (Arg) { seedRand(); TestFrame::reseed(); processing_generateFrame(); processing_generateMultichan(); processing_duplicateMultichan(); processing_manipulateMultichan(); processing_manipulateFrame(); processing_combineFrames(); testRand_simpleUsage(); } /** @test function to generate random test data frames */ void processing_generateFrame() { size_t frameNr = defaultGen.u64(); uint flavour = defaultGen.u64(); Buffer buff; CHECK (not buff->isSane()); ont::generateFrame (buff, frameNr, flavour); CHECK ( buff->isSane()); CHECK ( buff->isPristine()); CHECK (*buff == TestFrame(frameNr,flavour)); } /** @test function to generate an array of random test data frames * for consecutive channels */ void processing_generateMultichan() { size_t frameNr = defaultGen.u64(); uint flavour = defaultGen.u64(); uint channels = 1 + rani(50); CHECK (1 <= channels and channels <= 50); Buffer buff[50]; for (uint i=0; iisSane()); ont::generateMultichan (buff[0], channels, frameNr, flavour); for (uint i=0; iisPristine()); CHECK (*(buff[i]) == TestFrame(frameNr,flavour+i)); } } /** @test clone copy of multichannel test data */ void processing_duplicateMultichan() { size_t frameNr = defaultGen.u64(); uint flavour = defaultGen.u64(); uint channels = 1 + rani(50); Buffer srcBuff[50]; ont::generateMultichan (srcBuff[0], channels, frameNr, flavour); Buffer clone[50]; for (uint i=0; iisSane()); ont::duplicateMultichan (clone[0],srcBuff[0], channels); for (uint i=0; iisPristine()); CHECK (*(clone[i]) == *(srcBuff[i])); } } /** @test multichannel data hash-chain manipulation * - use multichannel pseudo random input data * - store away a clone copy before manipulation * - the #manipulateMultichan() operates in-place in the buffers * - each buffer has been marked with a new checksum afterwards * - and each buffer now differs from original state * - verify that corresponding data points over all channels * have been linked by a hashcode-chain, seeded with the `param` * and then consecutively hashing in data from each channel. */ void processing_manipulateMultichan() { size_t frameNr = defaultGen.u64(); uint flavour = defaultGen.u64(); uint channels = 1 + rani(50); Buffer buff[50], refData[50]; ont::generateMultichan (buff[0], channels, frameNr, flavour); // stash away a copy of the test data for verification ont::duplicateMultichan(refData[0],buff[0], channels); for (uint c=0; cisPristine()); uint64_t param = defaultGen.u64(); ont::manipulateMultichan(buff[0], channels, param); const uint SIZ = buff[0]->data64().size(); vector xlink(SIZ, param); // temporary storage for verifying the hash-chain for (uint c=0; cisSane()); // checksum matches CHECK (not buff[c]->isPristine()); // data was indeed changed CHECK (*(buff[c]) != *(refData[c])); for (auto& [i, link] : izip(xlink)) { auto const& refPoint = refData[c]->data64()[i]; lib::hash::combine (link, refPoint); CHECK (link != refPoint); CHECK (link == buff[c]->data64()[i]); } } } /** @test function to apply a numeric computation to test data frames; * @remark here basically the same hash-chaining is used as for #manipulateMultichan, * but only one hash-chain per data point is used and output is written to a different buffer. */ void processing_manipulateFrame() { size_t frameNr = defaultGen.u64(); uint flavour = defaultGen.u64(); Buffer iBuff, oBuff; iBuff.buildData(frameNr,flavour); oBuff.buildData(frameNr,flavour); CHECK (iBuff->isPristine()); CHECK (oBuff->isPristine()); uint64_t param = defaultGen.u64(); ont::manipulateFrame (oBuff, iBuff, param); CHECK ( oBuff->isValid()); CHECK (not oBuff->isPristine()); CHECK ( iBuff->isPristine()); for (auto [iDat,oDat] : zip (iBuff->data64() ,oBuff->data64())) { CHECK (oDat != iDat); uint64_t feed = param; lib::hash::combine (feed, iDat); CHECK (feed != param); CHECK (feed != iDat); CHECK (feed == oDat); } // can also process in-place ont::manipulateFrame (iBuff, iBuff, param); CHECK (not iBuff->isPristine()); CHECK ( iBuff->isValid()); CHECK (*iBuff == *oBuff); // second invocation exactly reproduced data from first invocation } /** @test function to mix two test data frames */ void processing_combineFrames() { size_t frameNr = defaultGen.u64(); uint flavour = defaultGen.u64(); Buffer i1Buff, i2Buff, oBuff; i1Buff.buildData(frameNr,flavour+0); i2Buff.buildData(frameNr,flavour+1); oBuff.buildData(); CHECK (i1Buff->isPristine()); CHECK (i2Buff->isPristine()); CHECK (oBuff->isPristine()); double mix = defaultGen.uni(); ont::combineFrames (oBuff, i1Buff, i2Buff, mix); CHECK ( oBuff->isValid()); CHECK (not oBuff->isPristine()); CHECK ( i1Buff->isPristine()); CHECK ( i2Buff->isPristine()); for (auto [oDat,i1Dat,i2Dat] : zip (oBuff->data() ,i1Buff->data() ,i2Buff->data())) CHECK (oDat == std::lround((1-mix)*i1Dat + mix*i2Dat)); // can also process in-place ont::combineFrames (i1Buff, i1Buff, i2Buff, mix); CHECK (not i1Buff->isPristine()); CHECK ( i1Buff->isValid()); CHECK (*i1Buff == *oBuff); // second invocation exactly reproduced data from first invocation } /** @test demonstrate simple usage of test-render setup * - access the TestRandOntology as singleton * - create a Spec record * - retrieve a functor bound suitably to invoke * data processing code from the TestRandOntology */ void testRand_simpleUsage() { auto spec = testRand().setupGenerator(); CHECK (spec.PROTO == "generate-TestFrame"_expect); // generate a binding as processing-functor auto procFun = spec.makeFun(); using Sig = lib::meta::_Fun::Sig; CHECK (showType() == "void (tuple, engine::test::TestFrame*)"_expect); // Behaves identical to processing_generateFrame() — see above... size_t frameNr = defaultGen.u64(); uint flavour = defaultGen.u64(); Buffer buff; CHECK (not buff->isSane()); procFun (make_tuple (frameNr,flavour), buff); CHECK ( buff->isSane()); CHECK ( buff->isPristine()); CHECK (*buff == TestFrame(frameNr,flavour)); ProcNode node{prepareNode(spec.nodeID()) .preparePort() .invoke(spec.procID(), procFun) .setParam(frameNr,flavour) .completePort() .build()}; CHECK (watch(node).getPortSpec(0) == "Test:generate(TestFrame)"_expect); BufferProvider& provider = DiagnosticBufferProvider::build(); BuffHandle buffHandle = provider.lockBuffer (provider.getDescriptorFor(sizeof(TestFrame))); uint port{0}; CHECK (not buffHandle.accessAs().isSane()); // Trigger Node invocation... buffHandle = node.pull (port, buffHandle, Time::ZERO, ProcessKey{0}); TestFrame& result = buffHandle.accessAs(); CHECK (result.isSane()); CHECK (result.isPristine()); CHECK (result == *buff); buffHandle.release(); } }; /** Register this test class... */ LAUNCHER (NodeDevel_test, "unit node"); }}} // namespace steam::engine::test