1 /**
2  * Copyright: (c) 2015-2020, Milofon Project.
3  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
4  * Author: <m.galanin@milofon.pro> Maksim Galanin
5  * Date: 2018-10-08
6  */
7 
8 module uninode.test_node;
9 
10 private
11 {
12     import std.algorithm.iteration : map;
13     import std.traits : isArray, isAssociativeArray;
14     import std.exception : assertThrown, collectException;
15     import std.meta : AliasSeq, allSatisfy;
16     import std.array : array;
17     import std.range : iota;
18     import std.conv : text;
19 
20     import uninode.node;
21 }
22 
23 
24 version (unittest)
25 {
26     void testInitNode(T)(T val, bool function(UniNode) @safe checker) @safe
27     {
28         UniNode node = UniNode(val);
29         assert (checker(node));
30 
31         const T cv = val;
32         const cnode = UniNode(cv);
33         assert (checker(cnode));
34 
35         static if (isArray!T)
36             immutable T iv = val.idup;
37         else static if (isAssociativeArray!T)
38             immutable T iv = () @trusted { return cast(immutable) val; } ();
39         else
40             immutable T iv = val;
41         immutable inode = UniNode(iv);
42         assert (checker(inode));
43     }
44 }
45 
46 
47 @("Should size normal")
48 @safe unittest
49 {
50     const node = UniNode(1);
51     assert (node.sizeof == 24);
52 }
53 
54 
55 @("Should allow create node")
56 @safe unittest
57 {
58     const node = UniNode(null);
59     assert (node.canNil);
60 
61     testInitNode!(int)(11, (n) => n.canInteger);
62     testInitNode!(uint)(11u, (n) => n.canUinteger);
63     testInitNode!(float)(1.1, (n) => n.canFloating);
64     testInitNode!(double)(1.1, (n) => n.canFloating);
65     testInitNode!(string)("hello", (n) => n.canText);
66     ubyte[2] bytes = [1, 2];
67     testInitNode(bytes, (n) => n.canRaw);
68     ubyte[] abytes = [1, 2];
69     testInitNode(abytes, (n) => n.canRaw);
70 
71     testInitNode([UniNode(1), UniNode(2)], (n) => n.canSequence);
72     testInitNode(["one": UniNode(1), "two": UniNode(2)], (n) => n.canMapping);
73 
74     const anode = UniNode(1, "one", 1.2);
75     assert (anode.canSequence);
76 
77     const arr = [UniNode(1), UniNode(2)];
78     const canode = UniNode(arr);
79     assert (canode.canSequence);
80 }
81 
82 
83 @("Should allow create sequence node")
84 @safe unittest
85 {
86     const start = iota(5).map!(i => UniNode(i)).array;
87 
88     const seq = UniNode(start);
89     static assert (is(typeof(seq.getSequence()) == const(UniNode[])));
90     assert (seq.canSequence);
91     assert (seq[0].get!int == 0); // opIndex
92     static assert (!__traits(compiles, () { seq[0] = 2; })); // no modify const
93 
94     auto mutSeq = UniNode(start);
95     static assert (is(typeof(mutSeq.getSequence()) == UniNode[]));
96     UniNode[] refArr = mutSeq.getSequence; // ref array
97 
98     assert (mutSeq[0].get!int == 0); // opIndex
99     mutSeq[0] = 101; // opIndexAssign
100     assert (mutSeq[0].get!int == 101); // opIndex
101     assert (refArr[0].get!int == 101); // array modify too
102 
103     mutSeq[0] = UniNode([UniNode(2), UniNode(3)]);
104     assert (mutSeq[0][1].get!int == 3); // nested opIndex
105 
106     mutSeq[0][1] = 4; // nested opIndexAssign
107     assert (mutSeq[0][1].get!int == 4);
108     assert (refArr[0].getSequence[1].get!int == 4); // ref array
109 
110     assert (mutSeq.getSequence.length == 5);
111     mutSeq ~= UniNode(5); // opOpAssign
112     assert (mutSeq.getSequence.length == 6);
113     assert (refArr.length == 5); // not modify ref array
114     assert (mutSeq[5].get!int == 5);
115 
116     mutSeq ~= [UniNode(55), UniNode(44)]; // opOpAssign array
117     assert (mutSeq.getSequence.length == 7);
118     assert (mutSeq[6][1].get!int == 44);
119 
120     const eNode = UniNode.emptySequence;
121     assert (eNode.canSequence);
122 
123     assert (start.length == 5);
124 }
125 
126 
127 @("Should allow create mapping node")
128 @safe unittest
129 {
130     const mapp = ["one": UniNode(1), "two": UniNode(2), "three": UniNode(3)];
131 
132     const cMapp = UniNode(mapp);
133     static assert (is(typeof(cMapp.getMapping()) == const(UniNode[string])));
134     assert (cMapp.canMapping);
135     assert (cMapp["one"].get!int == 1);
136     static assert (!__traits(compiles, () { cMapp["four"] = 4; }));
137 
138     auto mutMapp = UniNode(mapp);
139     static assert (is(typeof(mutMapp.getMapping()) == UniNode[string]));
140     assert (mutMapp.canMapping);
141     assert (mutMapp["two"].get!int == 2);
142     UniNode[string] refMapp = mutMapp.getMapping;
143 
144     assert (mutMapp["one"].get!int == 1); // opIndex
145     mutMapp["one"] = 101;
146     assert (mutMapp["one"].get!int == 101);
147     assert (refMapp["one"].get!int == 101);
148 
149     mutMapp["one"] = UniNode(["one": UniNode(11), "two": UniNode(12)]);
150     assert (mutMapp["one"]["two"].get!int == 12);
151 
152     mutMapp["one"]["one"] = 1010;
153     assert (mutMapp["one"]["one"].get!int == 1010);
154     assert (refMapp["one"].getMapping["one"].get!int == 1010);
155 
156     assert (mutMapp.getMapping.length == 3);
157     mutMapp["four"] = UniNode(4);
158     assert (mutMapp.getMapping.length == 4);
159     assert (refMapp.length == 4); // ref modify too
160     assert (mutMapp["four"].get!int == 4);
161     assert (mapp.length == 3); // start not modify
162 
163     const eMap = UniNode.emptyMapping;
164     assert (eMap.canMapping);
165 
166     mutMapp.remove("four");
167     assert (mutMapp.getMapping.length == 3);
168     assert (refMapp.length == 3); // ref modify too
169 
170     assert ("one" in cMapp);
171     auto cNode = "one" in cMapp;
172     assert (cNode.get!int == 1);
173     static assert (is(typeof(cNode) == const(UniNode)*));
174 
175     auto context = UniNode.emptyMapping;
176     context["loop"] = UniNode.emptyMapping;
177     context["loop"]["length"] = UniNode(1);
178 }
179 
180 
181 @("Should work protect number overflow")
182 @safe unittest
183 {
184     const minNode = UniNode(long.min);
185     auto e = collectException!UniNodeException(minNode.get!ubyte);
186     assert (e.msg == "Signed value less zero");
187 
188     const bigNode = UniNode(long.max);
189     e = collectException!UniNodeException(bigNode.get!ubyte);
190     assert (e.msg == "Conversion positive overflow");
191 
192     const uNode = UniNode(ulong.max);
193     e = collectException!UniNodeException(uNode.get!ubyte);
194     assert (e.msg == "Conversion positive overflow");
195 
196     assertThrown!UniNodeException(uNode.get!long);
197     assertThrown!UniNodeException(bigNode.get!(byte));
198 }
199 
200 
201 @("Should allow create unsigned integer node")
202 @safe unittest
203 {
204     foreach (TT; AliasSeq!(ubyte, ushort, uint, ulong))
205     {
206         TT v = cast(TT)11U;
207         const node = UniNode(v);
208         assert (node.canUinteger);
209         assert (node.get!TT == 11U);
210     }
211 }
212 
213 
214 @("Should allow create integer node")
215 @safe unittest
216 {
217     foreach (TT; AliasSeq!(byte, short, int, long))
218     {
219         TT v = -11;
220         const node = UniNode(v);
221         assert (node.canInteger);
222         assert (node.get!TT == -11);
223     }
224 }
225 
226 
227 @("Should allow boolean node")
228 @safe unittest
229 {
230     const node = UniNode(true);
231     assert (node.canBoolean);
232     assert (node.get!bool == true);
233 
234     const nodei = UniNode(0);
235     assert (nodei.canInteger);
236     assert (nodei.get!bool == false);
237 
238     const nodeu = UniNode(ulong.max);
239     assert (nodeu.canUinteger);
240     assert (nodeu.get!bool == true);
241 }
242 
243 
244 @("Should allow create floating node")
245 @safe unittest
246 {
247     foreach (TT; AliasSeq!(float, double))
248     {
249         TT v = 11.11;
250         const node = UniNode(v);
251         assert (node.canFloating);
252         assert (node.get!TT == cast(TT)11.11);
253     }
254 
255     const nodeu = UniNode(11u);
256     assert (nodeu.get!double == 11.0);
257 
258     const nodei = UniNode(-11);
259     assert (nodei.get!double == -11.0);
260 }
261 
262 
263 @("Should allow create string node")
264 @safe unittest
265 {
266     enum text = "hello";
267 
268     const node = UniNode(text);
269     assert(node.canText);
270     assert (node.get!string == text);
271 
272     ubyte[] bytes = new ubyte[text.length];
273     foreach (i, c; text)
274         bytes[i] = c;
275 
276     const nodeb = UniNode(bytes);
277     assert(nodeb.get!string == text);
278 }
279 
280 
281 @("Should allow create raw node")
282 @safe unittest
283 {
284     ubyte[] dynArr = [1, 2, 3];
285     auto node = UniNode(dynArr);
286     assert (node.canRaw);
287     assert (node.get!(ubyte[]) == [1, 2, 3]);
288 
289     ubyte[3] stArr = [1, 2, 3];
290     node = UniNode(stArr);
291     assert (node.canRaw);
292     assert (node.get!(ubyte[3]) == [1, 2, 3]);
293 
294     Bytes bb = [1, 2, 3];
295     node = UniNode(bb);
296     assert (node.canRaw);
297     assert (node.get!(ubyte[]) == [1, 2, 3]);
298 }
299 
300 
301 @("Should work numeric node")
302 @safe unittest
303 {
304     auto snode = UniNode(-10);
305     const exc = collectException!UniNodeException(snode.get!ulong);
306     assert (exc && exc.msg == "Signed value less zero");
307 
308     auto unode = UniNode(ulong.max);
309     const exc2 = collectException!UniNodeException(unode.get!long);
310     assert (exc2 && exc2.msg == "Unsigned value great max");
311 }
312 
313 
314 @("Should work system code")
315 @system unittest
316 {
317     const node = UniNode(1);
318     auto mnode = UniNode(["one": node, "two": node]);
319     const anode = UniNode([node, node]);
320 
321     ulong counter;
322     foreach (string key, const(UniNode) n; mnode.getMapping)
323         counter++;
324     assert (counter == mnode.getMapping.length);
325 
326     counter = 0;
327     foreach (ulong idx, const(UniNode) n; anode.getSequence)
328         counter++;
329     assert (counter == anode.getSequence.length);
330 
331     counter = 0;
332     foreach (const(UniNode) n; anode.getSequence)
333         counter++;
334     assert (counter == anode.getSequence.length);
335 }
336 
337 
338 @("Should work opApply for object")
339 @safe unittest
340 {
341     import std.algorithm.searching : canFind, all;
342 
343     string[] keys;
344     UniNode[] values;
345 
346     const cNode = UniNode(["one": UniNode(1), "two": UniNode(2)]);
347     foreach (string k, const(UniNode) n; cNode)
348     {
349         keys ~= k;
350         values ~= n;
351     }
352 
353     UniNode[string] mapp = ["tree": UniNode(3), "four": UniNode(4)];
354     UniNode mNode = UniNode(mapp);
355     foreach (string k, UniNode n; mNode)
356     {
357         keys ~= k;
358         values ~= n;
359     }
360 
361     assert (["one", "two", "tree", "four"].all!((i) => keys.canFind(i)));
362     assert ([UniNode(1), UniNode(2), UniNode(3), UniNode(4)].all!(
363                 (i) => values.canFind(i)));
364 }
365 
366 
367 @("Should work opApply for array")
368 @safe unittest
369 {
370     import std.algorithm.searching : canFind, all;
371 
372     auto arr = [UniNode(5), UniNode(7)];
373     auto node = UniNode(arr);
374     UniNode[] values;
375     foreach (const(UniNode) n; node)
376         values ~= n;
377 
378     assert (arr.all!((i) => values.canFind(i)));
379 }
380 
381 
382 @("Should work opApply for array with idx")
383 @safe unittest
384 {
385     import std.algorithm.searching : canFind, all;
386 
387     auto arr = [UniNode(5), UniNode(7), UniNode(3)];
388     auto node = UniNode(arr);
389     size_t summ;
390     UniNode[] values;
391     foreach (size_t i, UniNode n; node)
392     {
393         values ~= n;
394         summ += i;
395     }
396 
397     assert (arr.all!((i) => values.canFind(i)));
398     assert (summ == 3);
399 }
400 
401 
402 @("Should work opApply for array @system")
403 @system unittest
404 {
405     import std.algorithm.searching : canFind, all;
406 
407     auto arr = [UniNode(5), UniNode(7)];
408     const node = UniNode(arr);
409     UniNode[] values;
410     foreach (UniNode n; node)
411         values ~= n;
412 
413     assert (arr.all!((i) => values.canFind(i)));
414 }
415 
416 
417 @("Should work toHash")
418 @safe unittest
419 {
420     const node1 = UniNode(1);
421     assert (node1.toHash == 1);
422 
423     auto val = "hello";
424     const nodes = UniNode(val);
425     assert (nodes.toHash == val.hashOf);
426 }
427 
428 
429 @("Should work function default value")
430 @safe unittest
431 {
432     string val;
433     void fun(UniNode node = UniNode(""))
434     {
435         val = node.get!string;
436     }
437 
438     fun();
439     assert (val == "");
440     fun(UniNode("1"));
441     assert (val == "1");
442 }
443 
444 
445 @("Should work any memory types")
446 @safe unittest
447 {
448     immutable inode = UniNode("immutable");
449     const cnode = UniNode("const");
450     const node = UniNode("auto");
451     assert (inode.length == 9);
452     assert (cnode.length == 5);
453     assert (node.length == 4);
454 }
455 
456 
457 @("Should allow match node")
458 @safe unittest
459 {
460     import std.format : fmt = format;
461 
462     alias allMatch = match!(
463         (bool val) => fmt!"got %s"(val),
464         (byte val) => fmt!"got integer %s"(val),
465         (ubyte val) => fmt!"got unsigned integer %s"(val),
466         (float val) => fmt!"got float val %s"(val),
467         (Bytes val) => fmt!"got bytes %s"(val),
468         (string val) => fmt!"got string '%s'"(val),
469         (const(UniNode)[] seq) => fmt!"got seq len %s"(seq.length),
470         (const(UniNode[string]) mapp) => fmt!"got mapping len %s"(mapp.length),
471         () { return "got empty"; }
472     );
473 
474     void testNode(UniNode node, string msg)
475     {
476         const val = allMatch(node);
477         assert (val == msg);
478     }
479 
480     testNode(UniNode(), "got empty");
481     testNode(UniNode(12), "got integer 12");
482     testNode(UniNode(11u), "got unsigned integer 11");
483     testNode(UniNode(1.1), "got float val 1.1");
484     ubyte[2] bytes = [1, 2];
485     testNode(UniNode(bytes), "got bytes [1, 2]");
486     testNode(UniNode("hello"), "got string 'hello'");
487     testNode(UniNode([UniNode(1), UniNode(2)]), "got seq len 2");
488     testNode(UniNode(["one": UniNode(1), "two": UniNode(2)]), "got mapping len 2");
489 }
490 
491 
492 @("Should work length operator")
493 @safe unittest
494 {
495     assert (UniNode("hello").length == 5);
496     ubyte[] bytes = [1, 2, 3];
497     assert (UniNode(bytes).length == 3);
498     assert (UniNode([UniNode(1), UniNode(2)]).length == 2);
499     assert (UniNode(["one": UniNode(1), "two": UniNode(2)]).length == 2);
500 }
501 
502 
503 @("Should allow template match node")
504 @safe unittest
505 {
506     const node = UniNode();
507     const val = node.match!(
508             (val) => "tpl",
509         );
510     assert (val == "tpl");
511 
512     bool flag = false;
513     node.match!(
514             (val) { flag = true; },
515         );
516     assert (flag);
517 }
518 
519 
520 @("Should work opEquals")
521 @safe unittest
522 {
523     const node1 = UniNode(1);
524     const node2 = UniNode(1u);
525     assert (node1.opEquals(node2));
526     assert (node2.opEquals(node1));
527     assert (node1 == node2);
528 
529     auto n1 = UniNode(1);
530     auto n2 = UniNode("1");
531     auto n3 = UniNode(1);
532 
533     assert (n1 == n3);
534     assert (n1 != n2);
535     assert (n1 != UniNode(3));
536 
537     assert (UniNode([n1, n2, n3]) != UniNode([n2, n1, n3]));
538     assert (UniNode([n1, n2, n3]) == UniNode([n1, n2, n3]));
539 
540     assert (UniNode(["one": n1, "two": n2]) == UniNode(["one": n1, "two": n2]));
541 }
542 
543 
544 @("Should work toString method")
545 @safe unittest
546 {
547     auto intNode = UniNode(int.max);
548     auto uintNode = UniNode(uint.max);
549     auto fNode = UniNode(float.nan);
550     auto textNode = UniNode("node");
551     auto boolNode = UniNode(true);
552     ubyte[] bytes = [1, 2, 3];
553     auto binNode = UniNode(bytes);
554     auto nilNode = UniNode();
555 
556     auto arrNode = UniNode([intNode, fNode, textNode, nilNode]);
557     const objNode = UniNode([
558             "i": intNode,
559             "ui": uintNode,
560             "f": fNode,
561             "text": textNode,
562             "bool": boolNode,
563             "bin": binNode,
564             "nil": nilNode,
565             "arr": arrNode]);
566 
567     assert (objNode.text == "{i:int(2147483647), bool:bool(true), "
568             ~ "text:text(node), arr:[int(2147483647), float(nan), text(node), "
569             ~ "nil], nil:nil, ui:uint(4294967295), bin:raw([1, 2, 3]), f:float(nan)}");
570 }
571 
572 
573 @("Should work function parameters")
574 @safe unittest
575 {
576     static int initializer(int start, int end, out UniNode result)
577     {
578         result = UniNode.emptySequence;
579         foreach (int i; start .. end)
580             result ~= UniNode(i);
581         return 0;
582     }
583 
584     UniNode ret;
585     assert (!initializer(1, 5, ret));
586     assert (ret.canSequence);
587     assert (ret.length == 4);
588 
589     ret.match!(
590             (ref UniNode[] arr) { arr ~= UniNode(6); },
591             () {}
592         );
593     assert (ret.length == 5);
594 
595     auto mapp = UniNode.emptyMapping;
596     mapp["two"] = UniNode(33);
597     mapp.match!(
598         (ref UniNode[string] mapp) { mapp = ["one": UniNode(1)]; },
599         () {}
600     );
601     assert (mapp["one"].get!int == 1);
602 }
603 
604 
605 @("Should work require")
606 @safe unittest
607 {
608     UniNode mapp = UniNode.emptyMapping;
609     auto req = mapp.require("one", 1);
610     assert (req == UniNode(1));
611     req = mapp.require("one", 11);
612     assert (req == UniNode(1));
613 }
614 
615 
616 @("Should work opApply modify")
617 @safe unittest
618 {
619     UniNode mapp = UniNode(["one": UniNode(1), "two": UniNode(2)]);
620     foreach (string k, ref UniNode n; mapp)
621         n = UniNode(k);
622     assert (mapp["one"] == UniNode("one"));
623 
624     UniNode seq = UniNode([UniNode(1), UniNode(2)]);
625     foreach (size_t idx, ref UniNode n; seq)
626         n = UniNode(idx * 4);
627     assert (seq[1] == UniNode(4));
628 }
629 
630 
631 @("Should work opt method")
632 @system unittest
633 {
634     immutable node = UniNode(1);
635     assert (!node.opt!int.isNull);
636     assert (node.opt!string.isNull);
637     assert (node.opt!(UniNode[]).isNull);
638     assert (node.opt!(UniNode[string]).isNull);
639 
640     UniNode anode = UniNode([UniNode(1), UniNode(2)]);
641     assert (!anode.opt!(UniNode[]).isNull);
642     assert (!anode.optSequence.isNull);
643     assert (node.optSequence.isNull);
644 
645     UniNode mnode = UniNode(["one": UniNode(1), "two": UniNode(2)]);
646     assert (!mnode.opt!(UniNode[string]).isNull);
647     assert (!mnode.optMapping.isNull);
648     assert (node.optMapping.isNull);
649 }
650 
651 
652 @("Should work getOrElse method")
653 @safe unittest
654 {
655     immutable node = UniNode(1);
656     assert (node.getOrElse(2) == 1);
657     assert (node.getOrElse("h") == "h");
658 }
659