From 3bfe8f33e093eff0783a74bdeb22f0549c3343ed Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 20 Nov 2024 05:50:44 +0100 Subject: [PATCH] Invocation: implement and verify extended verification Since each `TestFrame` now has a metadata header, we can store an additional data checksum there, so that it is now possible both to detect if data is in pristine state, or if it matches a changed state recorded in the additional checksum. So we have now three different levels of verification isSane:: consistent metadata header found isValid:: metadata header found and checksum there matches data isPristine:: in addition, the data is exactly as generated from the `(frameNr,family)` --- tests/15library.tests | 2 +- tests/core/steam/engine/testframe-test.cpp | 111 ++++++++--- tests/core/steam/engine/testframe.cpp | 193 +++++++++++------- tests/core/steam/engine/testframe.hpp | 29 ++- wiki/thinkPad.ichthyo.mm | 221 ++++++++++++--------- 5 files changed, 361 insertions(+), 195 deletions(-) diff --git a/tests/15library.tests b/tests/15library.tests index 118e9932f..6a8e4c654 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -598,7 +598,7 @@ return: 0 END -PLANNED "Several elements" SeveralBuilder_test < frame.data().size()); // additional metadata placed behind + } + + + void + verifyDataContent() + { TestFrame frameA; TestFrame frameB; TestFrame frameC(5); @@ -90,6 +111,11 @@ namespace test { CHECK (frameA != frameC); CHECK (frameB != frameC); + CHECK (frameA.data() == frameB.data()); + CHECK (frameA.data() != frameC.data()); + for (uint i=0; iisDead()); - CHECK (frame->isAlive()); - CHECK (frame->isSane()); + CHECK (not frame->isDead()); + CHECK ( frame->isAlive()); + CHECK ( frame->isValid()); frame->~TestFrame(); - CHECK ( TestFrame::isDead (frame)); - CHECK (!TestFrame::isAlive (frame)); + CHECK ( TestFrame::isDead (frame)); + CHECK (not TestFrame::isAlive (frame)); + CHECK ( frame->isValid()); + CHECK ( frame->isSane()); } @@ -151,8 +202,8 @@ namespace test { { thisFrames[i].swap (prevFrames[i]); thisFrames[i].reset (new TestFrame(nr, i)); - CHECK (thisFrames[i]->isSane()); - CHECK (prevFrames[i]->isSane()); + CHECK (thisFrames[i]->isPristine()); + CHECK (prevFrames[i]->isPristine()); CHECK (prevFrames[i]->isAlive()); CHECK (*thisFrames[i] != *prevFrames[i]); // differs from predecessor within the same channel @@ -165,18 +216,18 @@ namespace test { } } } - /** @test the table of test frames - * computed on demand */ + + /** @test the table of test frames computed on demand */ void useFrameTable() { - TestFrame& frX = testData(3,50); - TestFrame& frY = testData(3,25); - TestFrame& frZ = testData(3,50); + TestFrame& frX = testData(50,3); + TestFrame& frY = testData(50,2); + TestFrame& frZ = testData(50,3); - CHECK (frX.isSane()); - CHECK (frY.isSane()); - CHECK (frZ.isSane()); + CHECK (frX.isPristine()); + CHECK (frY.isPristine()); + CHECK (frZ.isPristine()); CHECK (frX != frY); CHECK (frX == frZ); @@ -185,14 +236,22 @@ namespace test { CHECK (isSameObject (frX, frZ)); corruptMemory(&frZ,40,20); - CHECK (!frX.isSane()); - CHECK (!testData(3,50).isSane()); - CHECK ( testData(3,51).isSane()); - CHECK ( testData(3,49).isSane()); + CHECK (not frX.isPristine()); + CHECK (not testData(50,3).isPristine()); + CHECK ( testData(51,3).isPristine()); + CHECK ( testData(49,3).isPristine()); + + char c = testData(49,3).data()[5]; // some arbitrary content TestFrame::reseed(); - CHECK ( testData(3,50).isSane()); + CHECK ( testData(50,3).isPristine()); + CHECK (c != testData(49,3).data()[5]); // content regenerated with different seed + + TestFrame o{49,3}; // all data content is reproducible with the new seed + CHECK (not isSameObject(o, testData(49,3))); + CHECK (o == testData(49,3)); + CHECK (o.data()[5] == testData(49,3).data()[5]); } }; diff --git a/tests/core/steam/engine/testframe.cpp b/tests/core/steam/engine/testframe.cpp index ba379b200..8a6a3fe35 100644 --- a/tests/core/steam/engine/testframe.cpp +++ b/tests/core/steam/engine/testframe.cpp @@ -12,7 +12,22 @@ * *****************************************************************/ /** @file testframe.cpp - ** Implementation of fake data frames to support unit testing + ** Implementation of fake data frames to support unit testing. + ** The data generation is based on a _discriminator seed value,_ + ** which is computed as a linear combination of a statically fixed anchor-seed + ** combined with the family-number and sequence number. Based on this seed, + ** the contents are then filled by a pseudo-random sequence. + ** @note while initially drawn from real entropy, the anchor-seed can be + ** reset from the default PRNG, which allows to establish a totally + ** deterministically setup from test code, because the test itself + ** can seed the default PRNG and thus establish a reproducible state. + ** + ** Additionally, beyond this basic test-data feature, the contents can be + ** manipulated freely, and a new checksum can be stored in the metadata, + ** which allows to build pseudo media computation functions with a + ** reproducible effect — so that the proper invocation of several + ** computation steps invoked deep down in the render engine can + ** be verified after completing a test invocation. */ @@ -31,8 +46,9 @@ namespace steam { -namespace engine { -namespace test { +namespace engine{ +namespace test { + namespace err = lumiera::error; using util::unConst; using std::vector; @@ -105,7 +121,7 @@ namespace test { stampHeader() { static const HashVal MARK = lib::entropyGen.hash() - | 0b1000'1000'1000'1000'1000'1000'1000'1000; + | 0b1000'1000'1000'1000'1000'1000'1000'1000; //////////////////////////////TICKET #722 : not portable because HashVal ≡ size_t — should it be? return MARK; } @@ -201,13 +217,7 @@ namespace test { TestFrame& - testData (uint seqNr) - { - return accessTestFrame (seqNr, 0); - } - - TestFrame& - testData (uint chanNr, uint seqNr) + testData (uint seqNr, uint chanNr) { return accessTestFrame (seqNr,chanNr); } @@ -227,7 +237,7 @@ namespace test { TestFrame::reseed() { testFrames.reset(); - drawSeed (lib::defaultGen); + dataSeed = drawSeed (lib::defaultGen); } @@ -251,8 +261,8 @@ namespace test { TestFrame::TestFrame (uint seq, uint family) : header_{seq,family} { + buildData(); ASSERT (0 < header_.distinction); - header_.checksum = buildData(); ENSURE (CREATED == header_.stage); ENSURE (isPristine()); } @@ -269,7 +279,7 @@ namespace test { TestFrame::operator= (TestFrame const& o) { if (not isAlive()) - throw new error::Logic ("target TestFrame already dead or unaccessible"); + throw err::Logic ("target TestFrame already dead or unaccessible"); if (not util::isSameAdr (this, o)) { data() = o.data(); @@ -280,43 +290,41 @@ namespace test { } + /** + * Sanity check on the metadata header. + * @remark Relevant to detect memory corruption or when accessing some + * arbitrary memory location, which may or may not actually hold a TestFrame. + * Based on the assumption that it is unlikely that some random memory location + * just happens to hold our [marker word](\ref stampHeader()). + * @note this is only the base level of verification, because in addition + * \ref isValid verifies the checksum and \ref isPristine additionally + * recomputes the data generation to see if it matches the Meta::distinction + */ + bool + TestFrame::Meta::isPlausible() const + { + return _MARK_ == stampHeader() + and stage <= DISCARDED; + } + TestFrame::Meta& TestFrame::accessHeader() { + if (not header_.isPlausible()) + throw err::Invalid{"TestFrame: missing or corrupted metadata"}; return header_; } TestFrame::Meta const& TestFrame::accessHeader() const { - ///////////////////////OOO detect if valid header present and else throw return unConst(this)->accessHeader(); } TestFrame::StageOfLife TestFrame::currStage() const { - ///////////////////////OOO detect if valid header present and then access - } - - /** @note performing an unchecked conversion of the given - * memory location to be accessed as TestFrame. - * The sanity of the data found at that location - * is checked as well, not only the lifecycle flag. - */ - bool - TestFrame::isAlive (void* memLocation) - { - TestFrame& candidate (accessAsTestFrame (memLocation)); - return candidate.isSane() - && candidate.isAlive(); - } - - bool - TestFrame::isDead (void* memLocation) - { - TestFrame& candidate (accessAsTestFrame (memLocation)); - return candidate.isSane() - && candidate.isDead(); + return header_.isPlausible()? header_.stage + : DISCARDED; } bool @@ -327,67 +335,83 @@ namespace test { && candidate == *this; } + bool + TestFrame::Meta::operator== (Meta const&o) const + { + return isPlausible() and o.isPlausible() + and stage == o.stage + and checksum == o.checksum + and distinction == o.distinction; + } + bool TestFrame::contentEquals (TestFrame const& o) const { - for (uint i=0; i getHash; + for (char const& dat : data()) + lib::hash::combine (checksum, getHash (dat)); + return checksum; } + /** @remark can be used to mark a manipulated new content as _valid_ */ + HashVal + TestFrame::markChecksum() + { + return accessHeader().checksum = computeChecksum(); + } + + bool TestFrame::hasValidChecksum() const { - ////////////////////////////////////////////OOO actually compute checksum and verify - } - - bool - TestFrame::isAlive() const - { - return (CREATED == currStage()) - || (EMITTED == currStage()); - } - - bool - TestFrame::isDead() const - { - return (DISCARDED == currStage()); + return accessHeader().checksum == computeChecksum(); } bool TestFrame::isSane() const { - return ( (CREATED == currStage()) - ||(EMITTED == currStage()) - ||(DISCARDED == currStage())) - && verifyData(); + return header_.isPlausible(); } bool @@ -399,9 +423,42 @@ namespace test { bool TestFrame::isPristine() const + { + return isValid() + and matchDistinction(); + } + + bool + TestFrame::isAlive() const { return isSane() - and hasValidChecksum(); + and not isDead(); + } + + bool + TestFrame::isDead() const + { + return isSane() + and (DISCARDED == currStage()); + } + + /** @note performing an unchecked conversion of the given + * memory location to be accessed as TestFrame. + * The sanity of the data found at that location + * is checked as well, not only the lifecycle flag. + */ + bool + TestFrame::isAlive (void* memLocation) + { + TestFrame& candidate (accessAsTestFrame (memLocation)); + return candidate.isAlive(); + } + + bool + TestFrame::isDead (void* memLocation) + { + TestFrame& candidate (accessAsTestFrame (memLocation)); + return candidate.isDead(); } diff --git a/tests/core/steam/engine/testframe.hpp b/tests/core/steam/engine/testframe.hpp index 0d9d932f0..31d2756c2 100644 --- a/tests/core/steam/engine/testframe.hpp +++ b/tests/core/steam/engine/testframe.hpp @@ -12,7 +12,21 @@ */ /** @file testframe.hpp - ** Unit test helper to generate fake test data frames + ** Unit test helper to generate fake test data frames. + ** Each TestFrame holds a 1k buffer of byte data, which can be + ** verified, accessed and manipulated to emulate media computations. + ** A [metadata header](\ref steam::engine::test::TestFrame::Meta) is placed + ** in memory behind the working buffer, which allows to detect data corruption + ** and stores a lifecycle phase and a data checksum. + ** + ** The contents of each TestFrame are filled on creation with pseudo-random data, + ** which is created from a _discriminator seed,_ based on a »family« and a »frame-nr« + ** within the family. Due to the deterministic nature of these computations, the + ** _pristine state_ of any frame can be determined. But the payload data is accessible + ** and can be manipulated, and a new [checksum can be recorded](\ref TestFrame::markChecksum). + ** + ** For ease of testing, a static store of TestFrame instances is maintained internally, + ** and an arbitrary memory location can be treated as TestFrame. */ @@ -68,6 +82,8 @@ namespace test { StageOfLife stage; Meta (uint seq, uint family); + bool isPlausible() const; + bool operator== (Meta const&) const; }; /** inline storage buffer for the payload media data */ @@ -86,6 +102,9 @@ namespace test { TestFrame (TestFrame const&); TestFrame& operator= (TestFrame const&); + /** recompute and store checksum based on current contents */ + HashVal markChecksum(); + /** Helper to verify that a given memory location holds * an active TestFrame instance (created, not yet destroyed) * @return true if the TestFrame datastructure is intact and @@ -114,8 +133,8 @@ namespace test { private: bool contentEquals (TestFrame const& o) const; - bool verifyData() const; - HashVal buildData(); + bool matchDistinction() const; + void buildData(); Meta& accessHeader(); Meta const& accessHeader() const; StageOfLife currStage() const; @@ -131,9 +150,7 @@ namespace test { * test frames filled with different yet reproducible pseudo random data. * Client code is free to access and corrupt this data. */ - TestFrame& testData (uint seqNr); - - TestFrame& testData (uint chanNr, uint seqNr); + TestFrame& testData (uint seqNr =0, uint chanNr =0); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 32829ca5a..607ee0ec8 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -17871,9 +17871,7 @@ - - - +

ScrollWindowPos == wo befindet sich das Scroll-Window (sichtbarer Ausschnitt) innerhalb eines größeren Intervalles, welches vom Widget dargestellt wird @@ -18742,9 +18740,7 @@ - - - +

Fazit: ja die Lösung funktioniert und erscheint stabil @@ -18996,9 +18992,7 @@ - - - +

auch hier mehrstufige Prüfung... @@ -19552,9 +19546,7 @@ - - - +

  • @@ -20223,9 +20215,7 @@ - - - +

    ...den eingebauten Debug-Meldungen zufolge zu urteilen @@ -21451,9 +21441,7 @@ - - - +

    wie ich's auch drehe und wende, daran führt kein Weg vorbei. Weil die Referenz eben nicht wirklich global ist, sondern einen root-Kontext darstellt (nämlich die umschließende Timeline). @@ -24436,9 +24424,7 @@ - - - +

    wird komplett erledigt durch TrackBody::establishTrackSpace @@ -42071,9 +42057,7 @@ - - - +

    hinreichend große Fenster (ab ein paar hunder Pixeln) können die ganze Zeitdomäne abdecken @@ -43371,9 +43355,7 @@ - - - +

    • @@ -44253,9 +44235,7 @@ - - - +

      man muß Floating-point runden wenn man glatte Werte will @@ -45937,9 +45917,7 @@ - - - +

      (GlobalCtx)->InteractionDirector->Navigator @@ -46086,9 +46064,7 @@ - - - +

      block[__element][--modifier] @@ -46231,9 +46207,7 @@ - - - +

      vom tangible initiiert @@ -56402,8 +56376,9 @@ - + + @@ -56463,11 +56438,16 @@ + + + + + @@ -56758,8 +56738,8 @@ - - + + @@ -56777,8 +56757,7 @@ Diskriminator-ID ≔ (seq+1) * (dataSeed+family)

      - -
      +
      @@ -56788,8 +56767,7 @@ Erläuterung: jede »family« hat ein eigenes Stepping

      - -
      +
      @@ -56800,8 +56778,7 @@ ...weil es dann passieren könnte, daß für bestimmte Familien die Frames sich nicht mehr unterscheiden

      - - +
      @@ -56820,17 +56797,17 @@ - - + + - - + + - - + + @@ -56851,12 +56828,14 @@ - - + + - - - + + + + + @@ -56874,26 +56853,26 @@ - - + + - - + + - - + + - - + + - - + + - + @@ -56902,8 +56881,9 @@

      +
      - + @@ -56912,8 +56892,9 @@

      +
      - + @@ -56922,10 +56903,51 @@

      +
      + + + + + + + + + + + + + + + + + + + + + + + + +

      + frameNr und seqNr - Parameter waren vertauscht bei testData() +

      +

      + Das habe ich erst bemerkt, als ich tatsächlich dazu noch einen frei stehenden TestFrame mit gleichen Koordinaten erzeugt und verglichen habe... +

      +

      + GnaGnaGna +

      + + +
      + +
      +
      +
      @@ -56951,13 +56973,13 @@ - - - - - - - + + + + + + + @@ -57001,10 +57023,15 @@ - + + + + - + + + @@ -92272,11 +92299,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
      - + - - + + @@ -92342,7 +92369,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
      - + @@ -92352,9 +92379,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
      - - - + + + + + + + @@ -92363,8 +92394,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

      +
      - + @@ -92373,14 +92405,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

      +
      - - + + - - - + + +