LUMIERA.clone/tests/lib/par/parameter-domain-test.cpp
Ichthyostega 0217fc9dbe Parameters: demonstrate type conversions and opaque value assignment
Largely this test covers already established functionality.
It is ''somewhat difficult'' to prove that an opaque type is present,
beyond showing that values are truncated or accommodated.

At the beginning of the implementation-draft, I considered a double-dispatch,
but concluded that this would be too much overhead for the standard case;
the drafted visitor-style dispatch was "parked" as `Domain::transferTo`.
It turned out that this implementation is not directly usable, since it
assumes that »the value buffer« is accessible, albeit as opaque ValBuff&.

As the design matured, I decided that the presence of a »Buffer« should better
not be assumed, and thus flipped the high-level-APIs around. But it is
easily possible to implement this double-dispatch scheme anew on top of
the now settled implementation scheme:
 - `Disposition::assignValueTo (Disposition&)` invoked on the source side
 - implemented in `ParamData<VAL, IFA>::assignValueTo (Disposition&)`,
   and there delegating to the templated front-end setter
   `Disposition::setVal(X)` (which performs the second dispatch step)

It remains to be seen if such an opaque value assignement is required in practice
2026-10-19 00:05:15 +02:00

294 lines
10 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.

/*
ParameterDomain(Test) - document the handling of parameter type domain
Copyright (C)
2026, 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 parameter-domain-test.cpp
** unit test \ref ParameterDomain_test
*/
#include "test/run.hpp"
//#include "test/test-helper.hpp"
#include "lib/par/domain.hpp"
#include "lib/par/param-type.hpp"
#include "test/diagnostic-output.hpp"////////////TODO
//#include <utility>
//#include <string>
//#include <vector>
#include <concepts>
//using std::string;
//using std::vector;
//using std::swap;
namespace lib {
namespace par {
namespace test{
// using lumiera::error::LUMIERA_ERROR_LOGIC;
using std::floating_point;
namespace {//Test helpers....
template<typename NUM>
constexpr NUM _MAX = std::numeric_limits<NUM>::max();
template<typename NUM>
constexpr NUM _MIN = std::numeric_limits<NUM>::lowest();
template<floating_point FLO>
constexpr FLO _EPSILON = std::numeric_limits<FLO>::epsilon();
}//(End)Test helpers
/**************************************************************************//**
* @test cover properties of generic parameter containers.
*/
class ParameterDomain_test : public Test
{
virtual void
run (Arg)
{
seedRand();
basics();
verify_valueLimits();
verify_valueAccess();
verify_valueSetting();
verify_opaqueAssign();
}
void
basics()
{
}
/** @test base operation to bracket a given value
* so that it fits within another target type's value domain.
* @note properly handles the signed vs. unsigned mismatch, so that
* conversion from signed to unsigned can not wrap.
*/
void
verify_valueLimits()
{
// Generic check if the first (target) type can hold
// only a limited part of the second (source) type's domain
CHECK ((sub_domain<uint,int> == true));
CHECK ((sub_domain<int,uint> == true));
CHECK ((sub_domain<short,long> == true));
CHECK ((sub_domain<long,short> == false));
CHECK ((sub_domain<float,double> == true));
CHECK ((sub_domain<double,float> == false));
// Limit a value to within a target type's domain
// (without actually converting into the target type)
CHECK (preClamp<int> (23) == 23);
CHECK (preClamp<int> (-55) == -55);
CHECK (preClamp<uint> (23) == 23);
CHECK (preClamp<uint>(-55) == 0);
CHECK (preClamp<short> (_MAX<int64_t>) == 32767);
CHECK (preClamp<short> (_MIN<int64_t>) == -32768);
CHECK (preClamp<ushort> (_MAX<float>) == 65535);
CHECK (preClamp<ushort> (_MIN<float>) == 0 );
// the return value has the same type as the argument...
CHECK (preClamp<float> (_MAX<uint64_t>) == _MAX<uint64_t>);
CHECK (preClamp<float> (_MIN<double>) == double(_MIN<float>) );
// Warning: the following is not standard C++, yet supported on GCC / CLang / libstdc++
static_assert (_MAX<float> < _MAX<unsigned __int128>);
CHECK (float(_MAX<unsigned __int128>) == "inf"_expect );
CHECK ((unsigned __int128)_MAX<float> == "340282346638528859811704183484516925440"_expect );
CHECK (preClamp<float> (_MAX<unsigned __int128>) == (unsigned __int128)_MAX<float>);
// Note: a trigger at 0.5+ε is built into the clamp towards bool
CHECK (preClamp<bool> (0.5) == 0 );
CHECK (preClamp<bool> (0.5+_EPSILON<double>) == 1 );
CHECK (preClamp<bool> (_MIN<double>) == 0 );
// Systematic coverage of signed ⟷ unsigned
CHECK (preClamp<int8_t> (_MAX<int16_t>) == 127);
CHECK (preClamp<int8_t> ( int16_t{0}) == 0);
CHECK (preClamp<int8_t> (_MIN<int16_t>) == -128);
CHECK (preClamp<int8_t> (_MAX<uint16_t>) == 127);
CHECK (preClamp<int8_t> (_MIN<uint16_t>) == 0);
CHECK (preClamp<uint8_t> (_MAX<int16_t>) == 255);
CHECK (preClamp<uint8_t> ( int16_t{0}) == 0);
CHECK (preClamp<uint8_t> (_MIN<int16_t>) == 0);
CHECK (preClamp<int16_t> (_MAX<int8_t>) == 127);
CHECK (preClamp<int16_t> ( int8_t{0}) == 0);
CHECK (preClamp<int16_t> (_MIN<int8_t>) == -128);
CHECK (preClamp<int16_t> (_MAX<uint8_t>) == 255);
CHECK (preClamp<int16_t> (_MIN<uint8_t>) == 0);
CHECK (preClamp<uint16_t> (_MAX<int8_t>) == 127);
CHECK (preClamp<uint16_t> ( int8_t{0}) == 0);
CHECK (preClamp<uint16_t> (_MIN<int8_t>) == 0);
CHECK (preClamp<int8_t> (_MAX<int8_t>) == 127);
CHECK (preClamp<int8_t> ( int8_t{0}) == 0);
CHECK (preClamp<int8_t> (_MIN<int8_t>) == -128);
CHECK (preClamp<int8_t> (_MAX<uint8_t>) == 127);
CHECK (preClamp<int8_t> (_MIN<uint8_t>) == 0);
CHECK (preClamp<uint8_t> (_MAX<int8_t>) == 127);
CHECK (preClamp<uint8_t> ( int8_t{0}) == 0);
CHECK (preClamp<uint8_t> (_MIN<int8_t>) == 0);
}
template<typename X>
using FullDomain = BaseDomain<X, Domain>;
/** @test base function to access the contents of an opaque buffer
* with known type, and convert into another target type.
* - the base interface #Domain represents an opaque type
* - it exposes #TypeHandler interfaces for a fixed selection
* of common elementary #BaseTypes
* - here we use a `BaseDomain<int>` as actual implementation,
* yet knowledge regarding the type `int` is erased and any
* access is based solely on a `Domain&`
* - an opaque source ValBuf is passed explicitly
* @remark this covers the value access capability of a
* type handler interface, working on external storage.
*/
void
verify_valueAccess()
{
FullDomain<int> domInt;
CHECK (sizeof(FullDomain<int>) <= sizeof(void*)); // only the VTable
// use the generic interface for access...
Domain& dom{domInt};
int val = 1 + rani (1000);
ValBuff& src = asValBuff (val);
uint target1{0};
CHECK (not target1);
dom.extractAs (target1, src);
CHECK (target1 == uint(val));
float target2{0};
dom.extractAs (target2, src);
CHECK (target2 == float(val));
// value clamped to target domain...
val = _MIN<int>;
uint64_t target3{55};
dom.extractAs (target3, src);
CHECK (target3 == _MIN<uint64_t>);
}
/** @test base function to assign a directly given data value
* into an opaque buffer with known (yet different) type.
* @remark since the type handler interface covers both reading
* and writing, here the roles are reversed here, so that the
* target of the assignment is the opaque buffer, whose actual
* type remains hidden behind the #Domain interface.
*/
void
verify_valueSetting()
{
FullDomain<int> domInt;
Domain& dom{domInt};
int val_in_buff = 0;
ValBuff& target = asValBuff (val_in_buff);
uint src1 = 1 + rani (1000);
dom.conform (target, src1);
CHECK (val_in_buff == int(src1));
double src2 = ranRange (1,100);
CHECK (1 <= src2 and src2 < 100);
dom.conform (target, src2);
CHECK (val_in_buff == int(floor (src2)));
// value clamped to target domain...
auto mini = _MIN<int64_t>;
dom.conform (target, mini);
CHECK (val_in_buff == _MIN<int>);
}
/** @test value transfer between opaque buffers with double-dispatch.
* - use several typed values, yet access these only marked as `ValBuff`
* - the call must be passed through _two virtual functions_, in order
* to re-gain the fully typed context; this is the »visitor pattern«
* @remark the API might seem bewildering; this is due to passing both source
* and target as reference to _some value buffer_, since this API describes
* only a data type disposition, while the actual data storage is defined
* as derived parameter data type.
* @todo 10/2026 it seems this was built only to explore the underlying mechanism.
*/
void
verify_opaqueAssign()
{
double double_val = ranRange (2,100);
int64_t i64_val{0};
bool boo{false};
FullDomain<double> ddom;
FullDomain<int64_t> idom;
FullDomain<bool> bdom;
Domain& d1{ddom};
Domain& d3{bdom};
CHECK (i64_val == 0);
d1.transferTo (asValBuff(double_val), idom, asValBuff(i64_val));
CHECK (i64_val == int64_t(floor (double_val)));
CHECK (not boo);
d1.transferTo (asValBuff(double_val), bdom, asValBuff(boo));
CHECK (boo);
CHECK (double_val != 1.0);
d3.transferTo (asValBuff(boo), ddom, asValBuff(double_val));
CHECK (double_val == 1.0);
}
};
/** Register this test class... */
LAUNCHER (ParameterDomain_test, "unit lib");
}}} // namespace lib::par::test