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
294 lines
10 KiB
C++
294 lines
10 KiB
C++
/*
|
||
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
|