Lumiera  0.pre.03
»edit your freedom«
hetero-data-test.cpp
1 /*
2  HeteroData(Test) - verify maintaining chained heterogeneous data in local storage
3 
4  Copyright (C)
5  2024, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
19 #include "lib/test/run.hpp"
20 #include "lib/hetero-data.hpp"
21 #include "lib/meta/trait.hpp"
22 #include "lib/test/test-helper.hpp"
24 #include "lib/util.hpp"
25 
26 #include <string>
27 
28 
29 namespace lib {
30 namespace test{
31 
32  using std::string;
33  using meta::is_Subclass;
34  using util::isSameObject;
35  using util::isSameAdr;
36  using util::getAdr;
37 
38 
39 
40 
41  /******************************************************************/
52  class HeteroData_test : public Test
53  {
54 
55  virtual void
56  run (Arg)
57  {
58  simpleUsage();
62  }
63 
64 
65  void
66  simpleUsage()
67  {
68  using F = lib::HeteroData<uint,double>; // define type of the front-end segment
69  auto h1 = F::build (1,2.3); // build the front-end, including first data tuple
70  using C = F::Chain<bool,string>; // define a constructor type for a follow-up segment
71  auto b2 = C::build (true, "Ψ"); // build this follow-up segment free-standing
72  b2.linkInto(h1); // link it as second segment into the chain
73  C::AccessorFor<string> get4; // get an accessor functor (picked by value type)
74  CHECK (get4(h1) == "Ψ"); // use accessor on front-type (involves force-cast)
75  }
76 
77 
79  void
81  {
82  using Block1 = HeteroData<uint,double>;
83  CHECK ((is_Subclass<Block1::NewFrame, std::tuple<uint,double>>()));
84 
85  auto b1 = Block1::build (42, 1.61803);
86  CHECK (1.61803 == b1.get<1>());
87  CHECK (42 == b1.get<0>());
88  CHECK (showType<Block1::Elm_t<0>>() == "uint"_expect);
89  CHECK (showType<Block1::Elm_t<1>>() == "double"_expect);
90 
91  Block1 b2;
92  CHECK (0.0 == b2.get<1>());
93  b2.get<1>() = 3.14;
94  CHECK (3.14 == b2.get<1>());
95 
96  CHECK (2 == std::tuple_size_v<Block1::NewFrame::Tuple>); // referring to the embedded tuple type
97  CHECK (2 == std::tuple_size_v<Block1::NewFrame>); // StorageFrame itself complies to the C++ tuple protocol
98  CHECK (2 == std::tuple_size_v<Block1>); // likewise for the complete HeteroData Chain
99 
100  auto& [_,p] = b2; // can use structured bindings...
101  CHECK (p == 3.14);
102  p = 3.14159;
103  CHECK (3.14159 == b2.get<1>());
104  }
105 
106 
108  void
110  {
111  using Block1 = HeteroData<uint>;
112  CHECK ((is_Subclass<Block1::NewFrame, std::tuple<uint>>()));
113 
114  using Constructor = Block1::Chain<double,string>;
115  using Block2 = Constructor::NewFrame;
116  CHECK ((is_Subclass<Block2, std::tuple<double, string>>()));
117 
118  auto b1 = Block1::build (41);
119  auto b2 = Constructor::build (1.61, "Φ");
120  b2.linkInto(b1);
121 
122  using Chain2 = Constructor::ChainType;
123  Chain2& chain2 = Constructor::recast (b1);
124  CHECK (b1.size() == 1);
125  CHECK (chain2.size() == 3);
126 
127  CHECK (41 == chain2.get<0>());
128  CHECK (1.61 == chain2.get<1>());
129  CHECK ("Φ" == chain2.get<2>());
130 
131  chain2.get<0>()++;
132  chain2.get<1>() = (1 + sqrt(5)) / 2;
133 
134  CHECK (b1.get<0>() == 42);
135  CHECK (chain2.get<0>() == 42);
136  CHECK (std::get<0> (b2) == "1.618034"_expect);
137 
138  CHECK (isSameObject (chain2.get<0>() ,b1.get<0>()));
139  CHECK (isSameObject (chain2.get<2>() ,std::get<1>(b2)));
140 
141  CHECK (1 == std::tuple_size_v<Block1::NewFrame::Tuple>); // referring to the embedded tuple type
142  CHECK (1 == std::tuple_size_v<Block1::NewFrame>);
143  CHECK (1 == std::tuple_size_v<Block1>);
144 
145  CHECK (2 == std::tuple_size_v<Block2::Tuple>); // referring to the embedded tuple type
146  CHECK (2 == std::tuple_size_v<Block2>);
147 
148  CHECK (3 == std::tuple_size_v<Chain2>);
149  CHECK ((showType<std::tuple_element_t<0, Chain2>>() == "uint"_expect));
150  CHECK ((showType<std::tuple_element_t<1, Chain2>>() == "double"_expect));
151  CHECK ((showType<std::tuple_element_t<2, Chain2>>() == "string"_expect));
152 
153  CHECK ((showType<std::tuple_element_t<0, Block2>>() == "double"_expect));
154  CHECK ((showType<std::tuple_element_t<1, Block2>>() == "string"_expect));
155 
156 // CHECK (std::get<0> (chain2) == "42"_expect); // std::tuple is inaccessible base of HeteroData
157  CHECK (std::get<0> (b2) == "1.618034"_expect);
158 // CHECK (std::get<1> (chain2) == "1.618034"_expect); // does not compile due to range restriction for the base tuple
159  // (as such this is correct — yet prevents definition of a custom get-function)
160  auto& [u0] = b1;
161  CHECK (u0 == "42"_expect);
162 
163  auto& [v0,v1] = b2; // b2 is typed as StorageFrame and thus the tuple base is accessible
164  CHECK (v0 == "1.618034"_expect);
165  CHECK (v1 == "Φ"_expect);
166 
167  auto& [x0,x1,x2] = chain2; // Note: structured binding on the fully typed chain uses the get<i>-Member
168  CHECK (x0 == "42"_expect);
169  CHECK (x1 == "1.618034"_expect);
170  CHECK (x2 == "Φ"_expect);
171 
172 // auto& [z0,z1,z2,z3] = chain2; // Error: 4 names provided for structured binding, while HeteroData... decomposes into 3 elements
173 // auto& [z0,z1,z2] = b1; // Error: HeteroData<Node<StorageFrame<0, uint>, NullType> >' decomposes into 1 element
174  }
175 
176 
177 
184  void
186  {
187  using Front = lib::HeteroData<uint,double>;
188  using Cons2 = Front::Chain<bool,string>;
189  using Data2 = Cons2::NewFrame;
190  using List2 = Cons2::ChainType;
191  using Acc4 = Cons2::AccessorFor<string>;
192  using Acc3 = Cons2::AccessorFor<bool>;
193  using Acc2 = Front::Accessor<1>;
194  using Acc1 = Front::Accessor<0>;
195  using Cons3 = Cons2::ChainExtent<CStr,string>;
196  using Data3 = Cons3::NewFrame;
197  using List3 = Cons3::ChainType;
198  using Acc5 = Cons3::AccessorFor<CStr>;
199  using Acc6 = Cons3::AccessorFor<string>;
200  CHECK (2 == Front::size());
201  CHECK (4 == List2::size());
202  CHECK (6 == List3::size());
203  //
204  // Note: up to now, not a single actual data element has been created
205  // Moreover, individual blocks can be created in any order...
206  Data2 d2;
207  d2.get<1>() = "Ψ";
208  Front front;
209  CHECK (front.get<1>() == 0.0);
210  front.get<1>() = 2.3;
211 
212  // Note the pitfall: Chain has not been connected yet,
213  // but the Accessors would assume otherwise
214  CHECK (Acc2::get(front) == 2.3);
215 // Acc3::get(front); // would cause NPE (or assertion failure on debug build)
216 
217  Acc4 get4; // could even instantiate the accessors...
218  CHECK (sizeof(get4) == 1); // (empty marker object with static methods)
219 // get4(front); // likewise NPE or assertion fail
220 
221  // Now link the second data element in properly
222  d2.linkInto(front);
223  CHECK (Acc1::get(front) == 0);
224  CHECK (Acc2::get(front) == 2.3);
225  CHECK (Acc3::get(front) == false);
226  CHECK (get4(front) == "Ψ");
227 
228  // further allocations can even be »elsewhere«
229  const void* loc;
230  {
231  Acc6 get6;
232  auto magic = Cons3::build("magic","cloud");
233  loc = getAdr(magic);
234  CHECK (magic.get<0>() == "magic"_expect);
235  CHECK (magic.get<1>() == "cloud"_expect);
236  // link into the cloud...
237  magic.linkInto(front);
238  CHECK (get6(front) == "cloud");
239  }// aaand...
240  // it's gone
241 
242  // Evil, evil...
244  Data3& d3 = evilSpace[0]; // note: working with left-over data from expired stack frame
245  CHECK (isSameAdr (d3, loc));
246  CHECK (d3.get<0>() == "magic"_expect); // const char* points into static data, so the chars are still there
247  new(&d3.get<1>()) string{"mushrooms"}; // the "cloud"-string was destroyed by magic's destructor
248 
249  auto& [v1,v2,v3,v4,v5,v6] = Cons3::recast(front); // using connectivity from the linked list connecting the segments
250  CHECK (v1 == "0"_expect);
251  CHECK (v2 == "2.3"_expect);
252  CHECK (v3 == "false"_expect);
253  CHECK (v4 == "Ψ"_expect);
254  CHECK (v5 == "magic"_expect);
255  CHECK (v6 == "mushrooms"_expect);
256 
257  v1 = 42;
258  v2 = 5.5;
259  v3 = true;
260  CHECK (front.get<0>() == 42);
261  CHECK (front.get<1>() == 5.5);
262  CHECK (d2.get<0>() == true);
263  CHECK (d2.get<1>() == "Ψ");
264 
265  CHECK (isSameAdr (Acc1::get(front), v1));
266  CHECK (isSameAdr (Acc2::get(front), v2));
267  CHECK (isSameAdr (Acc3::get(front), v3));
268  CHECK (isSameAdr (Acc4::get(front), v4));
269  CHECK (isSameAdr (Acc5::get(front), v5));
270  CHECK (isSameAdr (Acc6::get(front), v6));
271 
272  CHECK (not isSameAdr (front, v1));
273  CHECK (not isSameAdr (d2, v3));
274  CHECK (not isSameAdr (d3, v5));
275  // we can directly re-cast into another typed front-end
276  List3& fullChain = Cons3::recast(front);
277  CHECK (isSameAdr (fullChain.get<2>(), std::get<0>(d2)));
278  CHECK (isSameAdr (fullChain.get<3>(), std::get<1>(d2)));
279  CHECK (isSameAdr (fullChain.get<4>(), std::get<0>(d3)));
280  CHECK (isSameAdr (fullChain.get<5>(), std::get<1>(d3)));
281  CHECK (isSameAdr (fullChain.get<0>(), v1));
282  CHECK (isSameAdr (fullChain.get<1>(), v2));
283  CHECK (isSameAdr (fullChain.get<2>(), v3));
284  CHECK (isSameAdr (fullChain.get<3>(), v4));
285  CHECK (isSameAdr (fullChain.get<4>(), v5));
286  CHECK (isSameAdr (fullChain.get<5>(), v6));
287  // we can even use partially specified chains
288  List2& partChain = Cons2::recast(fullChain);
289  CHECK (isSameAdr (partChain.get<0>(), v1));
290  CHECK (isSameAdr (partChain.get<1>(), v2));
291  CHECK (isSameAdr (partChain.get<2>(), v3));
292  CHECK (isSameAdr (partChain.get<3>(), v4));
293 
294  // Note: basically we are still using stale memory,
295  // previously allocated to the "magic" block,
296  // and now covered by the UninitialisedStorage
297  CHECK (loc == & d3);
298  CHECK (loc < & v5);
299  CHECK (loc < & v6);
300 
301  // structural binding on partial chains is limited
302  CHECK (partChain.size() == 4);
303  auto& [w1,w2,w3,w4] = partChain;
304  CHECK (isSameObject (v1, w1));
305  CHECK (isSameObject (v2, w2));
306  CHECK (isSameObject (v3, w3));
307  CHECK (isSameObject (v4, w4));
308  }
309  };
310 
311 
313  LAUNCHER (HeteroData_test, "unit common");
314 
315 
316 }} // namespace lib::test
Maintain a chained sequence of heterogeneous data blocks without allocation.
Definition: run.hpp:40
Implementation namespace for support and library code.
Simplistic test class runner.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
Block of raw uninitialised storage with array like access.
A setup with chained data tuples residing in distributed storage.
Definition: hetero-data.hpp:92
A collection of frequently used helper functions to support unit testing.
A raw memory block with proper alignment and array access.
Helpers for type detection, type rewriting and metaprogramming.
string showType()
diagnostic type output, including const and similar adornments
bool isSameAdr(A const &a, B const &b)
compare plain object address identity, disregarding type.
Definition: util.hpp:411
verify compliance to an interface by subtype check
Definition: trait.hpp:323
bool isSameObject(A const &a, B const &b)
compare plain object identity, based directly on the referee&#39;s memory identities. ...
Definition: util.hpp:421