1 module unit_threaded.randomized.random;
2 
3 
4 /** This type will generate a $(D Gen!T) for all passed $(D T...).
5     Every call to $(D genValues) will call $(D gen) of all $(D Gen) structs
6     present in $(D values). The member $(D values) can be passed to every
7     function accepting $(D T...).
8 */
9 struct RndValueGen(T...)
10 {
11     import std.meta : staticMap;
12     import std.random: Random;
13 
14     /* $(D Values) is a collection of $(D Gen) types created through
15        $(D ParameterToGen) of passed $(T ...).
16     */
17     static if(is(typeof(T[0]) == string[])) {
18         alias generators = T[1 .. $];
19         string[] parameterNames = T[0];
20     } else {
21         alias generators = T;
22         string[T.length] parameterNames;
23     }
24 
25     alias Values = staticMap!(ParameterToGen, generators);
26 
27     /// Ditto
28     Values values;
29 
30     /* The constructor accepting the required random number generator.
31        Params:
32        rnd = The required random number generator.
33     */
34     this(Random* rnd) @safe
35     {
36         this.rnd = rnd;
37     }
38 
39     this(ref Random rnd) {
40         this.rnd = &rnd;
41     }
42 
43     /* The random number generator used to generate new value for all
44        $(D values).
45     */
46     Random* rnd;
47 
48     /** A call to this member function will call $(D gen) on all items in
49         $(D values) passing $(D the provided) random number generator
50     */
51     void genValues()
52     {
53         assert(rnd !is null);
54         foreach (ref it; this.values)
55         {
56             it.gen(*this.rnd);
57         }
58     }
59 
60     void toString(scope void delegate(const(char)[]) sink)
61     {
62         import std.format : formattedWrite;
63 
64         foreach (idx, ref it; values)
65         {
66             formattedWrite(sink, "'%s' = %s ", parameterNames[idx], it);
67         }
68     }
69 }
70 
71 ///
72 unittest
73 {
74     import unit_threaded.randomized.gen: Gen;
75     import std.random: Random;
76 
77     auto rnd = Random(1337);
78     auto generator = (&rnd).RndValueGen!(["i", "f"],
79                                          Gen!(int, 0, 10),
80                                          Gen!(float, 0.0, 10.0));
81     generator.genValues();
82 
83     static fun(int i, float f)
84     {
85         import std.conv: to;
86         assert(i >= 0 && i <= 10, i.to!string);
87         assert(f >= 0.0 && f <= 10.0, f.to!string);
88     }
89 
90     fun(generator.values);
91 }
92 
93 @("RndValueGen can be used without parameter names")
94 unittest
95 {
96     import unit_threaded.randomized.gen: Gen;
97     import std.random: Random;
98 
99     auto rnd = Random(1337);
100     auto generator = rnd.RndValueGen!(Gen!(int, 0, 10),
101                                       Gen!(float, 0.0, 10.0));
102     generator.genValues();
103 
104     static fun(int i, float f)
105     {
106         import std.conv: to;
107         assert(i >= 0 && i <= 10, i.to!string);
108         assert(f >= 0.0 && f <= 10.0, f.to!string);
109     }
110 
111     fun(generator.values);
112 }
113 
114 
115 unittest
116 {
117     import unit_threaded.randomized.gen: Gen;
118     import std.random: Random;
119 
120     static fun(int i, float f)
121     {
122         assert(i >= 0 && i <= 10);
123         assert(f >= 0.0 && i <= 10.0);
124     }
125 
126     auto rnd = Random(1337);
127     auto generator = (&rnd).RndValueGen!(["i", "f"],
128                                          Gen!(int, 0, 10),
129                                          Gen!(float, 0.0, 10.0));
130 
131     generator.genValues();
132     foreach (i; 0 .. 1000)
133     {
134         fun(generator.values);
135     }
136 }
137 
138 @("RndValueGen with int[]")
139 unittest {
140     import unit_threaded.randomized.gen: Gen;
141     import std.random: Random;
142 
143     void fun(int[] i) { }
144     auto rnd = Random(1337);
145     auto gen = rnd.RndValueGen!(Gen!(int[]));
146     gen.genValues;
147     fun(gen.values);
148 }
149 
150 /** A template that turns a $(D T) into a $(D Gen!T) unless $(D T) is
151     already a $(D Gen) or no $(D Gen) for given $(D T) is available.
152 */
153 template ParameterToGen(T)
154 {
155     import unit_threaded.randomized.gen: isGen, Gen;
156     static if (isGen!T)
157         alias ParameterToGen = T;
158     else static if (is(T : GenASCIIString!(S), S...))
159         alias ParameterToGen = T;
160     else {
161         static assert(__traits(compiles, Gen!T),
162                       "ParameterToGen does not handle " ~ T.stringof);
163         alias ParameterToGen = Gen!T;
164     }
165 }
166 
167 ///
168 unittest
169 {
170     alias GenInt = ParameterToGen!int;
171 
172     static fun(int i)
173     {
174         assert(i == 1337);
175     }
176 
177     GenInt a;
178     a.value = 1337;
179     fun(a);
180 }
181 
182 @("RndValueGen with user defined struct")
183 unittest {
184     import unit_threaded.randomized.gen: Gen;
185     import std.random: Random;
186 
187     struct Foo {
188         int i;
189         short s;
190     }
191 
192     auto rnd = Random(1337);
193     auto gen = rnd.RndValueGen!(Gen!Foo);
194 
195     foreach(_; 0 .. 5) // get rid of front-loaded uninteresting values
196         gen.genValues;
197 
198     void fun(Foo foo) {
199         import std.conv: text;
200         assert(foo == Foo(1125387415, -8003), text(foo));
201     }
202 
203     fun(gen.values);
204 }
205 
206 
207 unittest
208 {
209     import unit_threaded.randomized.gen: isGen;
210     import std.random: Random;
211     import std.meta : AliasSeq, staticMap;
212 
213     struct Foo {
214         int i;
215         double d;
216     }
217 
218     foreach (T; AliasSeq!(byte, ubyte, ushort, short, uint, int, ulong, long,
219                           float, double, real,
220                           string, wstring, dstring, Foo))
221     {
222         alias TP = staticMap!(ParameterToGen, T);
223         static assert(isGen!TP);
224     }
225 }