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