LUMIERA.clone/tests/vault/mem/buffer-metadata-test.cpp
Ichthyostega ea0ae5f78e BufferProvider: propagate the actual buffer-size info
This is a nasty new requirement, discovered as an aside while looking
into ways to resolve the ''crucial mismatch'' between the `OutputSlot`
and `BufferProvider`, using the proxy-implementation of `BufferProvider`
(thereby expanding on the recent draft with the `BufferProxyAdaptor`)

The point is, if the BufferMetadata will be used beyond the internal coordination
within BufferProvider, then it will be used as part of actual buffer storage.
And this implies that the actual size of the allocation may differ (be greater)
than the nominally required size (since we'll very likely employ some kind
of memory tiling, for reasons of performance).

This leads again to an analysis of `BufferMetadata` usage,
with the conclusion to add the actual storage size directly into the API,
but treat it as an ''extra'' and not integrated into the ''buffer type''

Phew ... quite complex alltogether
2026-03-16 23:45:24 +01:00

414 lines
14 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
BufferMetadata(Test) - properties of internal data buffer metadata
Copyright (C)
2011, 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 buffer-metadata-test.cpp
** unit test \ref BufferMetadata_test
*/
#include "lib/error.hpp"
#include "test/run.hpp"
#include "test/test-frame.hpp"
#include "test/test-helper.hpp"
#include "vault/mem/buffhandle.hpp"
#include "vault/mem/buffer-metadata.hpp"
#include "lib/util.hpp"
#include <memory>
using std::strncpy;
using std::unique_ptr;
using test::TestFrame;
using test::testData;
using util::isSameObject;
using util::isnil;
using lib::randStr;
namespace vault {
namespace mem {
namespace test {
using LERR_(FATAL);
using LERR_(INVALID);
using LERR_(LIFECYCLE);
namespace { // Test fixture
template<typename TY>
TY&
accessAs (metadata::Entry& entry)
{
TY* ptr = reinterpret_cast<TY*> (entry.access());
ASSERT (ptr);
return *ptr;
}
template<typename X>
Buff*
mark_as_Buffer(X& something)
{
return reinterpret_cast<Buff*> (std::addressof(something));
}
const size_t TEST_MAX_SIZE = 1024 * 1024;
HashVal JUST_SOMETHING = 123;
auto SOME_POINTER = mark_as_Buffer(JUST_SOMETHING);
}//(End) Test fixture and helpers
/***************************************************************//**
* @test verify the properties of the BufferMetadata records used
* internally within BufferProvider to attach additional
* organisational data to the exposed buffers.
*/
class BufferMetadata_test : public Test
{
size_t SIZE_A{0};
size_t SIZE_B{0};
/** common Metadata table to be tested */
unique_ptr<BufferMetadata> meta_;
virtual void
run (Arg)
{
seedRand();
SIZE_A = 1 + rani(TEST_MAX_SIZE);
SIZE_B = 1 + rani(TEST_MAX_SIZE);
CHECK (ensure_proper_fixture());
verifyBasicProperties();
verifyStandardCase();
verifyStateMachine();
}
bool
ensure_proper_fixture()
{
if (!meta_)
meta_.reset(new BufferMetadata("BufferMetadata_test"));
return (SIZE_A != SIZE_B)
and (JUST_SOMETHING != meta_->key(SIZE_A))
and (JUST_SOMETHING != meta_->key(SIZE_B))
;
}
void
verifyBasicProperties()
{
// retrieve some type keys
metadata::Key key = meta_->key(SIZE_A);
CHECK (key);
metadata::Key key1 = meta_->key(SIZE_A);
metadata::Key key2 = meta_->key(SIZE_B);
CHECK (key1);
CHECK (key2);
CHECK (key == key1);
CHECK (key != key2);
// access metadata entries
VERIFY_ERROR (INVALID, meta_->get(0));
VERIFY_ERROR (INVALID, meta_->get(JUST_SOMETHING));
CHECK ( & meta_->get(key));
CHECK ( & meta_->get(key1));
CHECK ( & meta_->get(key2));
CHECK ( isSameObject (meta_->get(key), meta_->get(key)));
CHECK ( isSameObject (meta_->get(key), meta_->get(key1)));
CHECK (!isSameObject (meta_->get(key), meta_->get(key2)));
// entries retrieved thus far were inactive (type only) entries
metadata::Entry& m1 = meta_->get(key);
CHECK (NIL == m1.state());
CHECK (!meta_->isLocked(key));
VERIFY_ERROR (LIFECYCLE, m1.mark(EMITTED));
VERIFY_ERROR (LIFECYCLE, m1.mark(FREE) );
// now create an active (buffer) entry
metadata::Entry& m2 = meta_->markLocked (key, SOME_POINTER);
CHECK (!isSameObject (m1,m2));
CHECK (NIL == m1.state());
CHECK (LOCKED == m2.state());
CHECK (SOME_POINTER == m2.access()); // buffer pointer associated
// entries are unique and identifiable by a memory address
HashVal keyX = meta_->key(key1, SOME_POINTER);
CHECK (meta_->isLocked(keyX));
CHECK (keyX != key1);
CHECK (keyX);
CHECK ( isSameObject (m1, meta_->get(key)));
CHECK ( isSameObject (m1, meta_->get(key1)));
CHECK ( isSameObject (m2, meta_->get(keyX)));
CHECK ( key1 == m2.parentKey());
// now able to do state transitions
CHECK (LOCKED == m2.state());
m2.mark(EMITTED);
CHECK (EMITTED == m2.state());
CHECK (SOME_POINTER == m2.access());
CHECK ( meta_->isLocked(keyX));
CHECK ( meta_->isKnown(keyX));
// but the FREE state is a dead end
m2.mark(FREE);
CHECK (!meta_->isLocked(keyX));
CHECK ( meta_->isKnown(keyX));
CHECK ( meta_->isKnown(key1));
VERIFY_ERROR (LIFECYCLE, m2.access());
VERIFY_ERROR (FATAL, m2.mark(LOCKED)); // buffer missing
CHECK ( isSameObject (m2, meta_->get(keyX))); // still accessible
// release buffer...
meta_->release(keyX);
CHECK (!meta_->isLocked(keyX));
CHECK (!meta_->isKnown(keyX));
CHECK ( meta_->isKnown(key1));
VERIFY_ERROR (INVALID, meta_->get(keyX)); // now unaccessible
}
/** @test simulate a standard buffer provider usage cycle
* @note to get the big picture, please refer to
* BufferProviderProtocol_test#verifyStandardCase()
* This testcase here performs precisely the metadata related
* operations necessary to carry out the standard case
* outlined at a higher level in the aforementioned test.
*/
void
verifyStandardCase()
{
// to build a descriptor for a buffer holding a TestFrame
TypeHandler attachTestFrame = TypeHandler::create<TestFrame>();
metadata::Key bufferType1 = meta_->key(sizeof(TestFrame), attachTestFrame);
// to build a descriptor for a raw buffer of size SIZE_B
metadata::Key rawBuffType = meta_->key(SIZE_B);
// to announce using a number of buffers of this type
LocalTag transaction1(1);
LocalTag transaction2(2);
bufferType1 = meta_->key(bufferType1, transaction1);
rawBuffType = meta_->key(rawBuffType, transaction2);
// these type keys are now handed over to the client,
// embedded into a Buffer Descriptor...
// later, when it comes to actually *locking* those buffers...
using RawBuffer = std::byte;
void* storage = malloc (2*SIZE_B);
// do the necessary memory allocations behind the scenes...
RawBuffer* rawbuf = (RawBuffer*)storage; // coding explicit allocations here for sake of clarity;
TestFrame* frames = new TestFrame[3]; // a real-world BufferProvider would use some kind of allocator
// track individual buffers by metadata entries
metadata::Entry& f0 = meta_->markLocked(bufferType1, mark_as_Buffer(frames[0]));
metadata::Entry& f1 = meta_->markLocked(bufferType1, mark_as_Buffer(frames[1]));
metadata::Entry& f2 = meta_->markLocked(bufferType1, mark_as_Buffer(frames[2]));
metadata::Entry& r0 = meta_->markLocked(rawBuffType, mark_as_Buffer(rawbuf[ 0]));
metadata::Entry& r1 = meta_->markLocked(rawBuffType, mark_as_Buffer(rawbuf[SIZE_B]));
CHECK (LOCKED == f0.state());
CHECK (LOCKED == f1.state());
CHECK (LOCKED == f2.state());
CHECK (LOCKED == r0.state());
CHECK (LOCKED == r1.state());
CHECK (transaction1 == f0.localTag());
CHECK (transaction1 == f1.localTag());
CHECK (transaction1 == f2.localTag());
CHECK (transaction2 == r0.localTag());
CHECK (transaction2 == r1.localTag());
auto adr =[](auto* x){ return reinterpret_cast<size_t>(x); };
CHECK (adr(f0.access()) == adr(frames+0));
CHECK (adr(f1.access()) == adr(frames+1));
CHECK (adr(f2.access()) == adr(frames+2));
CHECK (adr(r0.access()) == adr(rawbuf+0 ));
CHECK (adr(r1.access()) == adr(rawbuf+SIZE_B));
TestFrame defaultFrame;
CHECK (defaultFrame == f0.access());
CHECK (defaultFrame == f1.access());
CHECK (defaultFrame == f2.access());
// at that point, we'd return BuffHandles to the client
HashVal handle_f0(f0);
HashVal handle_f1(f1);
HashVal handle_f2(f2);
HashVal handle_r0(r0);
HashVal handle_r1(r1);
// client uses the buffers---------------------(Start)
accessAs<TestFrame> (f0) = testData(1);
accessAs<TestFrame> (f1) = testData(2);
accessAs<TestFrame> (f2) = testData(3);
CHECK (testData(1) == frames[0]);
CHECK (testData(2) == frames[1]);
CHECK (testData(3) == frames[2]);
CHECK (TestFrame::isAlive (f0.access()));
CHECK (TestFrame::isAlive (f1.access()));
CHECK (TestFrame::isAlive (f2.access()));
strncpy (& accessAs<char> (r0), randStr(SIZE_B - 1).c_str(), SIZE_B);
strncpy (& accessAs<char> (r1), randStr(SIZE_B - 1).c_str(), SIZE_B);
// client might trigger some state transitions
f0.mark(EMITTED);
f1.mark(EMITTED);
f1.mark(BLOCKED);
// client uses the buffers---------------------(End)
f0.mark(FREE); // note: implicitly invoking the embedded dtor
f1.mark(FREE);
f2.mark(FREE);
r0.mark(FREE);
r1.mark(FREE);
meta_->release(handle_f0);
meta_->release(handle_f1);
meta_->release(handle_f2);
meta_->release(handle_r0);
meta_->release(handle_r1);
CHECK (TestFrame::isDead (&frames[0])); // was destroyed implicitly
CHECK (TestFrame::isDead (&frames[1]));
CHECK (TestFrame::isDead (&frames[2]));
// manual cleanup of test allocations
delete[] frames;
free(storage);
CHECK (!meta_->isLocked(handle_f0));
CHECK (!meta_->isLocked(handle_f1));
CHECK (!meta_->isLocked(handle_f2));
CHECK (!meta_->isLocked(handle_r0));
CHECK (!meta_->isLocked(handle_r1));
}
void
verifyStateMachine()
{
// start with building a type key....
metadata::Key key = meta_->key(SIZE_A);
CHECK (NIL == meta_->get(key).state());
CHECK (meta_->get(key).isTypeKey());
CHECK (not meta_->isLocked(key));
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(LOCKED) );
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(EMITTED));
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(BLOCKED));
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(FREE) );
VERIFY_ERROR (LIFECYCLE, meta_->get(key).mark(NIL) );
// now build a concrete buffer entry
metadata::Entry& entry = meta_->markLocked (key, SOME_POINTER);
CHECK (LOCKED == entry.state());
CHECK (not entry.isTypeKey());
CHECK (SOME_POINTER == entry.access());
VERIFY_ERROR (FATAL, entry.mark(LOCKED) ); // invalid state transition
VERIFY_ERROR (FATAL, entry.mark(NIL) );
entry.mark (EMITTED); // valid transition
CHECK (EMITTED == entry.state());
CHECK (entry.isLocked());
VERIFY_ERROR (FATAL, entry.mark(LOCKED) );
VERIFY_ERROR (FATAL, entry.mark(EMITTED));
VERIFY_ERROR (FATAL, entry.mark(NIL) );
CHECK (EMITTED == entry.state());
entry.mark (FREE);
CHECK (FREE == entry.state());
CHECK (not entry.isLocked());
CHECK (not entry.isTypeKey());
VERIFY_ERROR (LIFECYCLE, entry.access() );
VERIFY_ERROR (FATAL, entry.mark(LOCKED) );
VERIFY_ERROR (FATAL, entry.mark(EMITTED));
VERIFY_ERROR (FATAL, entry.mark(BLOCKED));
VERIFY_ERROR (FATAL, entry.mark(FREE) );
VERIFY_ERROR (FATAL, entry.mark(NIL) );
// re-use buffer slot, start new lifecycle
auto* SOME_OTHER_LOCATION = mark_as_Buffer(*this);
entry.lock (SOME_OTHER_LOCATION);
CHECK (LOCKED == entry.state());
CHECK (entry.isLocked());
VERIFY_ERROR (LIFECYCLE, entry.lock (SOME_POINTER));
entry.mark (BLOCKED); // go directly to the blocked state
CHECK (BLOCKED == entry.state());
VERIFY_ERROR (FATAL, entry.mark(LOCKED) );
VERIFY_ERROR (FATAL, entry.mark(EMITTED) );
VERIFY_ERROR (FATAL, entry.mark(BLOCKED) );
VERIFY_ERROR (FATAL, entry.mark(NIL) );
CHECK (SOME_OTHER_LOCATION == entry.access());
entry.mark (FREE);
CHECK (not entry.isLocked());
VERIFY_ERROR (LIFECYCLE, entry.access() );
meta_->markLocked (key, SOME_POINTER);
//Note: we locked the key and this changed the existing entry
CHECK (entry.isLocked());
entry.mark (EMITTED);
entry.mark (BLOCKED);
CHECK (BLOCKED == entry.state());
CHECK (SOME_POINTER == entry.access());
// can't discard metadata, need to free first
VERIFY_ERROR (LIFECYCLE, meta_->release(entry) );
CHECK (meta_->isKnown(entry));
CHECK (entry.isLocked());
entry.mark (FREE);
meta_->release(entry);
CHECK (not meta_->isKnown(entry));
CHECK ( meta_->isKnown(key));
}
};
/** Register this test class... */
LAUNCHER (BufferMetadata_test, "unit engine");
}}} // namespace vault::mem::test