1 module unit_threaded.randomized.gen;
2 
3 template from(string moduleName) {
4     mixin("import from = " ~ moduleName ~ ";");
5 }
6 
7 
8 /* Return $(D true) if the passed $(D T) is a $(D Gen) struct.
9 
10 A $(D Gen!T) is something that implicitly converts to $(D T), has a method
11 called $(D gen) that is accepting a $(D ref Random).
12 
13 This module already brings Gens for numeric types, strings and ascii strings.
14 
15 If a function needs to be benchmarked that has a parameter of custom type a
16 custom $(D Gen) is required.
17 */
18 template isGen(T)
19 {
20     static if (is(T : Gen!(S), S...))
21         enum isGen = true;
22     else
23         enum isGen = false;
24 }
25 
26 ///
27 unittest
28 {
29     static assert(!isGen!int);
30     static assert(isGen!(Gen!(int, 0, 10)));
31 }
32 
33 private template minimum(T) {
34     import std.traits: isIntegral, isFloatingPoint, isSomeChar;
35     static if(isIntegral!T || isSomeChar!T)
36         enum minimum = T.min;
37     else static if (isFloatingPoint!T)
38         enum mininum = T.min_normal;
39     else
40         enum minimum = T.init;
41 }
42 
43 private template maximum(T) {
44     import std.traits: isNumeric;
45     static if(isNumeric!T)
46         enum maximum = T.max;
47     else
48         enum maximum = T.init;
49 }
50 
51 /** A $(D Gen) type that generates numeric values between the values of the
52 template parameter $(D low) and $(D high).
53 */
54 mixin template GenNumeric(T, T low, T high) {
55 
56     import std.random: Random;
57 
58     static assert(is(typeof(() {
59         T[] res = frontLoaded();
60     })), "GenNumeric needs a function frontLoaded returning " ~ T.stringof ~ "[]");
61 
62     alias Value = T;
63 
64     T value;
65 
66     T gen(ref Random gen)
67     {
68         import std.random: uniform;
69 
70         static assert(low <= high);
71 
72         this.value = _index < frontLoaded.length
73             ? frontLoaded[_index++]
74             : uniform!("[]")(low, high, gen);
75 
76         return this.value;
77     }
78 
79     ref T opCall()
80     {
81         return this.value;
82     }
83 
84     void toString(scope void delegate(const(char)[]) sink) @trusted
85     {
86         import std.format : formattedWrite;
87         import std.traits: isFloatingPoint;
88 
89         static if (isFloatingPoint!T)
90         {
91             static if (low == T.min_normal && high == T.max)
92             {
93                 formattedWrite(sink, "'%s'", this.value);
94             }
95         }
96         else static if (low == T.min && high == T.max)
97         {
98             formattedWrite(sink, "'%s'", this.value);
99         }
100         else
101         {
102             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value,
103                 low, high);
104         }
105     }
106 
107     alias opCall this;
108 
109 
110     private int _index;
111 }
112 
113 /** A $(D Gen) type that generates numeric values between the values of the
114 template parameter $(D low) and $(D high).
115 */
116 struct Gen(T, T low = minimum!T, T high = maximum!T) if (from!"std.traits".isIntegral!T)
117 {
118     private static T[] frontLoaded() @safe pure nothrow {
119         import std.algorithm: filter;
120         import std.array: array;
121         T[] values = [0, 1, T.min, T.max];
122         return values.filter!(a => a >= low && a <= high).array;
123     }
124 
125     mixin GenNumeric!(T, low, high);
126 }
127 
128 struct Gen(T, T low = 0, T high = 6.022E23) if(from!"std.traits".isFloatingPoint!T) {
129      private static T[] frontLoaded() @safe pure nothrow {
130         import std.algorithm: filter;
131         import std.array: array;
132          T[] values = [0, T.epsilon, T.min_normal, high];
133          return values.filter!(a => a >= low && a <= high).array;
134     }
135 
136     mixin GenNumeric!(T, low, high);
137 }
138 
139 @safe pure unittest {
140     import unit_threaded.asserts: assertEqual;
141     import std.random: Random;
142 
143     auto rnd = Random(1337);
144     Gen!int gen;
145     assertEqual(gen.gen(rnd), 0);
146     assertEqual(gen.gen(rnd), 1);
147     assertEqual(gen.gen(rnd), int.min);
148     assertEqual(gen.gen(rnd), int.max);
149     assertEqual(gen.gen(rnd), 1125387415); //1st non front-loaded value
150 }
151 
152 @safe unittest {
153     // not pure because of floating point flags
154     import unit_threaded.asserts: assertEqual;
155     import std.math: approxEqual;
156     import std.conv: to;
157     import std.random: Random;
158 
159     auto rnd = Random(1337);
160     Gen!float gen;
161     assertEqual(gen.gen(rnd), 0);
162     assertEqual(gen.gen(rnd), float.epsilon);
163     assertEqual(gen.gen(rnd), float.min_normal);
164     assert(approxEqual(gen.gen(rnd), 6.022E23), gen.value.to!string);
165     assert(approxEqual(gen.gen(rnd), 1.57791E23), gen.value.to!string);
166 }
167 
168 
169 @safe unittest {
170     // not pure because of floating point flags
171     import unit_threaded.asserts: assertEqual;
172     import std.math: approxEqual;
173     import std.conv: to;
174     import std.random: Random;
175 
176     auto rnd = Random(1337);
177     Gen!(float, 0, 5) gen;
178     assertEqual(gen.gen(rnd), 0);
179     assertEqual(gen.gen(rnd), float.epsilon);
180     assertEqual(gen.gen(rnd), float.min_normal);
181     assertEqual(gen.gen(rnd), 5);
182     assert(approxEqual(gen.gen(rnd), 1.31012), gen.value.to!string);
183 }
184 
185 /** A $(D Gen) type that generates unicode strings with a number of
186 charatacters that is between template parameter $(D low) and $(D high).
187 */
188 struct Gen(T, size_t low = 0, size_t high = 32) if (from!"std.traits".isSomeString!T)
189 {
190     import std.random: Random, uniform;
191 
192     static immutable T charSet;
193     static immutable size_t numCharsInCharSet;
194     alias Value = T;
195 
196     T value;
197     static this()
198     {
199         import std.array : array;
200         import std.uni : unicode;
201         import std.format : format;
202         import std.range : chain, iota;
203         import std.algorithm : map, joiner;
204         import std.conv : to;
205         import std.utf : count;
206 
207         Gen!(T, low, high).charSet = chain(
208             iota(0x21, 0x7E).map!(a => to!T(cast(dchar) a)),
209             iota(0xA1, 0x1EF).map!(a => to!T(cast(dchar) a)))
210             .joiner.array.to!T;
211         Gen!(T, low, high).numCharsInCharSet = count(charSet);
212     }
213 
214     T gen(ref Random gen)
215     {
216         static assert(low <= high);
217         import std.array : appender;
218         import std.utf : byDchar;
219 
220         if(_index < frontLoaded.length) {
221             value = frontLoaded[_index++];
222             return value;
223         }
224 
225         auto app = appender!T();
226         app.reserve(high);
227         size_t numElems = uniform!("[]")(low, high, gen);
228 
229         for (size_t i = 0; i < numElems; ++i)
230         {
231             size_t charIndex = uniform!("[)")(0, numCharsInCharSet, gen);
232             app.put(charSet[charIndex]);
233         }
234 
235         this.value = app.data;
236         return this.value;
237     }
238 
239     ref T opCall()
240     {
241         return this.value;
242     }
243 
244     void toString(scope void delegate(const(char)[]) sink)
245     {
246         import std.format : formattedWrite;
247 
248         static if (low == 0 && high == 32)
249         {
250             formattedWrite(sink, "'%s'", this.value);
251         }
252         else
253         {
254             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value,
255                            low, high);
256         }
257     }
258 
259     alias opCall this;
260 
261 private:
262 
263     int _index;
264 
265     T[] frontLoaded() @safe pure nothrow const {
266         import std.algorithm: filter;
267         import std.array: array;
268         T[] values = ["", "a", "é"];
269         return values.filter!(a => a.length >= low && a.length <= high).array;
270     }
271 }
272 
273 unittest
274 {
275     import std.meta : AliasSeq, aliasSeqOf;
276     import std.range : iota;
277     import std.array : empty;
278     import std.random: Random;
279     import unit_threaded.asserts;
280 
281     foreach (index, T; AliasSeq!(string, wstring, dstring)) {
282         auto r = Random(1337);
283         Gen!T a;
284         T expected = "";
285         assertEqual(a.gen(r), expected);
286         expected = "a";
287         assertEqual(a.gen(r), expected);
288         expected = "é";
289         assertEqual(a.gen(r), expected);
290         assert(a.gen(r).length > 1);
291     }
292 }
293 
294 /// DITTO This random $(D string)s only consisting of ASCII character
295 struct GenASCIIString(size_t low = 1, size_t high = 32)
296 {
297     import std.random: Random;
298 
299     static string charSet;
300     static immutable size_t numCharsInCharSet;
301 
302     string value;
303 
304     static this()
305     {
306         import std.array : array;
307         import std.uni : unicode;
308         import std.format : format;
309         import std.range : chain, iota;
310         import std.algorithm : map, joiner;
311                 import std.conv : to;
312                 import std.utf : byDchar, count;
313 
314         GenASCIIString!(low, high).charSet = to!string(chain(iota(0x21,
315             0x7E).map!(a => to!char(cast(dchar) a)).array));
316 
317         GenASCIIString!(low, high).numCharsInCharSet = count(charSet);
318     }
319 
320     string gen(ref Random gen)
321     {
322         import std.array : appender;
323         import std.random: uniform;
324 
325         if(_index < frontLoaded.length) {
326             value = frontLoaded[_index++];
327             return value;
328         }
329 
330         auto app = appender!string();
331         app.reserve(high);
332         size_t numElems = uniform!("[]")(low, high, gen);
333 
334         for (size_t i = 0; i < numElems; ++i)
335         {
336             size_t toSelect = uniform!("[)")(0, numCharsInCharSet, gen);
337             app.put(charSet[toSelect]);
338         }
339 
340         this.value = app.data;
341         return this.value;
342     }
343 
344     ref string opCall()
345     {
346         return this.value;
347     }
348 
349     void toString(scope void delegate(const(char)[]) sink)
350     {
351         import std.format : formattedWrite;
352 
353         static if (low == 0 && high == 32)
354         {
355             formattedWrite(sink, "'%s'", this.value);
356         }
357         else
358         {
359             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value,
360                 low, high);
361         }
362     }
363 
364     alias opCall this;
365 
366 private:
367 
368     int _index;
369 
370     string[] frontLoaded() @safe pure nothrow const {
371         return ["", "a"];
372     }
373 }
374 
375 
376 @safe unittest {
377     import unit_threaded.asserts;
378     import std.random: Random;
379 
380     auto rnd = Random(1337);
381     GenASCIIString!() gen;
382     assertEqual(gen.gen(rnd), "");
383     assertEqual(gen.gen(rnd), "a");
384     version(Windows)
385         assertEqual(gen.gen(rnd), "yt4>%PnZwJ*Nv3L5:9I#N_ZK");
386     else
387         assertEqual(gen.gen(rnd), "i<pDqp7-LV;W`d)w/}VXi}TR=8CO|m");
388 }
389 
390 struct Gen(T, size_t low = 1, size_t high = 1024)
391     if(from!"std.range.primitives".isInputRange!T && !from!"std.traits".isSomeString!T)
392 {
393 
394     import std.traits: Unqual, isIntegral, isFloatingPoint;
395     import std.range: ElementType;
396     import std.random: Random;
397 
398     alias Value = T;
399     alias E = Unqual!(ElementType!T);
400 
401     T value;
402     Gen!E elementGen;
403 
404     T gen(ref Random rnd) {
405         value = _index < frontLoaded.length
406             ? frontLoaded[_index++]
407             : genArray(rnd);
408         return value;
409     }
410 
411     alias value this;
412 
413 private:
414 
415     size_t _index;
416      //these values are always generated
417     T[] frontLoaded() @safe nothrow {
418         T[] ret = [[]];
419         return ret;
420     }
421 
422     T genArray(ref Random rnd) {
423         import std.array: appender;
424         import std.random: uniform;
425 
426         immutable length = uniform(low, high, rnd);
427         auto app = appender!T;
428         app.reserve(length);
429         foreach(i; 0 .. length) {
430             app.put(elementGen.gen(rnd));
431         }
432 
433         return app.data;
434     }
435 }
436 
437 static assert(isGen!(Gen!(int[])));
438 
439 
440 @("Gen!int[] generates random arrays of int")
441 @safe unittest {
442     import unit_threaded.asserts: assertEqual;
443     import std.random: Random;
444 
445     auto rnd = Random(1337);
446     auto gen = Gen!(int[], 1, 10)();
447 
448     // first the front-loaded values
449     assertEqual(gen.gen(rnd), []);
450     version(Windows)
451         assertEqual(gen.gen(rnd), [0, 1]);
452     else
453         assertEqual(gen.gen(rnd), [0, 1, -2147483648, 2147483647, 681542492, 913057000, 1194544295, -1962453543, 1972751015]);
454 }
455 
456 @("Gen!ubyte[] generates random arrays of ubyte")
457 @safe unittest {
458     import unit_threaded.asserts: assertEqual;
459     import std.random: Random;
460 
461     auto rnd = Random(1337);
462     auto gen = Gen!(ubyte[], 1, 10)();
463     assertEqual(gen.gen(rnd), []);
464 }
465 
466 
467 @("Gen!double[] generates random arrays of double")
468 @safe unittest {
469     import unit_threaded.asserts: assertEqual;
470     import std.random: Random;
471 
472     auto rnd = Random(1337);
473     auto gen = Gen!(double[], 1, 10)();
474 
475     // first the front-loaded values
476     assertEqual(gen.gen(rnd), []);
477     // then the pseudo-random ones
478     version(Windows)
479         assertEqual(gen.gen(rnd).length, 2);
480     else
481         assertEqual(gen.gen(rnd).length, 9);
482 }
483 
484 @("Gen!string[] generates random arrays of string")
485 @safe unittest {
486     import unit_threaded.asserts: assertEqual;
487     import std.random: Random;
488 
489     auto rnd = Random(1337);
490     auto gen = Gen!(string[])();
491 
492     assertEqual(gen.gen(rnd), []);
493     auto strings = gen.gen(rnd);
494     assert(strings.length > 1);
495     assertEqual(strings[1], "a");
496 }
497 
498 @("Gen!string[][] generates random arrays of string")
499 @safe unittest {
500     import unit_threaded.asserts: assertEqual;
501     import std.random: Random;
502 
503     auto rnd = Random(1337);
504     auto gen = Gen!(string[][])();
505 
506     assertEqual(gen.gen(rnd), []);
507     // takes too long
508     // auto strings = gen.gen(rnd);
509     // assert(strings.length > 1);
510 }
511 
512 
513 struct Gen(T) if(is(T == bool)) {
514     import std.random: Random;
515 
516     bool value;
517     alias value this;
518 
519     bool gen(ref Random rnd) @safe {
520         import std.random: uniform;
521         value = [false, true][uniform(0, 2, rnd)];
522         return value;
523     }
524 }
525 
526 @("Gen!bool generates random booleans")
527 @safe unittest {
528     import unit_threaded.asserts: assertEqual;
529     import std.random: Random;
530 
531     auto rnd = Random(1337);
532     auto gen = Gen!bool();
533 
534     assertEqual(gen.gen(rnd), true);
535     assertEqual(gen.gen(rnd), true);
536     assertEqual(gen.gen(rnd), false);
537     assertEqual(gen.gen(rnd), false);
538 }
539 
540 
541 struct Gen(T, T low = minimum!T, T high = maximum!T) if (from!"std.traits".isSomeChar!T)
542 {
543     private static T[] frontLoaded() @safe pure nothrow { return []; }
544     mixin GenNumeric!(T, low, high);
545 }
546 
547 
548 @("Gen char, wchar, dchar")
549 @safe unittest {
550     import unit_threaded.asserts: assertEqual;
551     import std.random: Random;
552 
553     {
554         auto rnd = Random(1337);
555         Gen!char gen;
556         assertEqual(cast(int)gen.gen(rnd), 151);
557     }
558     {
559         auto rnd = Random(1337);
560         Gen!wchar gen;
561         assertEqual(cast(int)gen.gen(rnd), 3223);
562     }
563     {
564         auto rnd = Random(1337);
565         Gen!dchar gen;
566         assertEqual(cast(int)gen.gen(rnd), 3223);
567     }
568 }
569 
570 private template AggregateTuple(T...) {
571     import unit_threaded.randomized.random: ParameterToGen;
572     import std.meta: staticMap;
573     alias AggregateTuple = staticMap!(ParameterToGen, T);
574 }
575 
576 struct Gen(T) if(from!"std.traits".isAggregateType!T) {
577 
578     import std.traits: Fields;
579     import std.random: Random;
580 
581     AggregateTuple!(Fields!T) generators;
582 
583     alias Value = T;
584     Value value;
585 
586     T gen(ref Random rnd) @safe {
587         static if(is(T == class))
588             if(value is null)
589                 value = new T;
590 
591         foreach(i, ref g; generators) {
592             value.tupleof[i] = g.gen(rnd);
593         }
594 
595         return value;
596     }
597 
598     inout(T) opCall() inout {
599         return this.value;
600     }
601 
602     alias opCall this;
603 
604 }
605 
606 @("struct")
607 @safe unittest {
608     import unit_threaded.asserts: assertEqual;
609     import std.random: Random;
610 
611     struct Foo {
612         int i;
613         string s;
614     }
615 
616     auto rnd = Random(1337);
617     Gen!Foo gen;
618     assertEqual(gen.gen(rnd), Foo(0, ""));
619     assertEqual(gen.gen(rnd), Foo(1, "a"));
620     assertEqual(gen.gen(rnd), Foo(int.min, "é"));
621 }
622 
623 @("class")
624 @safe unittest {
625     import unit_threaded.asserts: assertEqual;
626     import std.random: Random;
627 
628     static class Foo {
629         this() {}
630         this(int i, string s) { this.i = i; this.s = s; }
631         override string toString() @safe const pure nothrow {
632             import std.conv;
633             return text(`Foo(`, i, `, "`, s, `")`);
634         }
635         override bool opEquals(Object _rhs) @safe const pure nothrow {
636             auto rhs = cast(Foo)_rhs;
637             return i == rhs.i && s == rhs.s;
638         }
639         int i;
640         string s;
641     }
642 
643     auto rnd = Random(1337);
644     Gen!Foo gen;
645     assertEqual(gen.gen(rnd), new Foo(0, ""));
646     assertEqual(gen.gen(rnd), new Foo(1, "a"));
647     assertEqual(gen.gen(rnd), new Foo(int.min, "é"));
648 }