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 assertEqual(gen.gen(rnd), [0, 1, -2147483648, 2147483647, 681542492, 913057000, 1194544295, -1962453543, 1972751015]); 429 } 430 431 @("Gen!ubyte[] generates random arrays of ubyte") 432 @safe unittest { 433 import unit_threaded.asserts: assertEqual; 434 auto rnd = Random(1337); 435 auto gen = Gen!(ubyte[], 1, 10)(); 436 assertEqual(gen.gen(rnd), []); 437 } 438 439 440 @("Gen!double[] generates random arrays of double") 441 @safe unittest { 442 import unit_threaded.asserts: assertEqual; 443 444 auto rnd = Random(1337); 445 auto gen = Gen!(double[], 1, 10)(); 446 447 // first the front-loaded values 448 assertEqual(gen.gen(rnd), []); 449 // then the pseudo-random ones 450 version(Windows) 451 assertEqual(gen.gen(rnd).length, 2); 452 else 453 assertEqual(gen.gen(rnd).length, 9); 454 } 455 456 @("Gen!string[] generates random arrays of string") 457 @safe unittest { 458 import unit_threaded.asserts: assertEqual; 459 460 auto rnd = Random(1337); 461 auto gen = Gen!(string[])(); 462 463 assertEqual(gen.gen(rnd), []); 464 auto strings = gen.gen(rnd); 465 assert(strings.length > 1); 466 assertEqual(strings[1], "a"); 467 } 468 469 @("Gen!string[][] generates random arrays of string") 470 @safe unittest { 471 import unit_threaded.asserts: assertEqual; 472 473 auto rnd = Random(1337); 474 auto gen = Gen!(string[][])(); 475 476 assertEqual(gen.gen(rnd), []); 477 // takes too long 478 // auto strings = gen.gen(rnd); 479 // assert(strings.length > 1); 480 } 481 482 483 struct Gen(T) if(is(T == bool)) { 484 bool value; 485 alias value this; 486 bool gen(ref Random rnd) @safe { 487 value = [false, true][uniform(0, 2, rnd)]; 488 return value; 489 } 490 } 491 492 @("Gen!bool generates random booleans") 493 @safe unittest { 494 import unit_threaded.asserts: assertEqual; 495 496 auto rnd = Random(1337); 497 auto gen = Gen!bool(); 498 499 assertEqual(gen.gen(rnd), true); 500 assertEqual(gen.gen(rnd), true); 501 assertEqual(gen.gen(rnd), false); 502 assertEqual(gen.gen(rnd), false); 503 } 504 505 506 struct Gen(T, T low = minimum!T, T high = maximum!T) if (isSomeChar!T) 507 { 508 private static T[] frontLoaded() @safe pure nothrow { return []; } 509 mixin GenNumeric!(T, low, high); 510 } 511 512 513 @("Gen char, wchar, dchar") 514 @safe unittest { 515 import unit_threaded.asserts: assertEqual; 516 { 517 auto rnd = Random(1337); 518 Gen!char gen; 519 assertEqual(cast(int)gen.gen(rnd), 151); 520 } 521 { 522 auto rnd = Random(1337); 523 Gen!wchar gen; 524 assertEqual(cast(int)gen.gen(rnd), 3223); 525 } 526 { 527 auto rnd = Random(1337); 528 Gen!dchar gen; 529 assertEqual(cast(int)gen.gen(rnd), 3223); 530 } 531 } 532 533 private template AggregateTuple(T...) { 534 import unit_threaded.randomized.random: ParameterToGen; 535 import std.meta: staticMap; 536 alias AggregateTuple = staticMap!(ParameterToGen, T); 537 } 538 539 struct Gen(T) if(isAggregateType!T) { 540 541 import std.traits: Fields; 542 AggregateTuple!(Fields!T) generators; 543 544 alias Value = T; 545 546 T gen(ref Random rnd) @safe { 547 static if(is(T == class)) 548 auto ret = new T; 549 else 550 T ret; 551 552 foreach(i, ref g; generators) { 553 ret.tupleof[i] = g.gen(rnd); 554 } 555 556 return ret; 557 } 558 } 559 560 @("struct") 561 @safe unittest { 562 import unit_threaded.asserts: assertEqual; 563 564 struct Foo { 565 int i; 566 string s; 567 } 568 569 auto rnd = Random(1337); 570 Gen!Foo gen; 571 assertEqual(gen.gen(rnd), Foo(0, "")); 572 assertEqual(gen.gen(rnd), Foo(1, "a")); 573 assertEqual(gen.gen(rnd), Foo(int.min, "é")); 574 } 575 576 @("class") 577 @safe unittest { 578 import unit_threaded.asserts: assertEqual; 579 580 static class Foo { 581 this() {} 582 this(int i, string s) { this.i = i; this.s = s; } 583 override string toString() @safe const pure nothrow { 584 import std.conv; 585 return text(`Foo(`, i, `, "`, s, `")`); 586 } 587 override bool opEquals(Object _rhs) @safe const pure nothrow { 588 auto rhs = cast(Foo)_rhs; 589 return i == rhs.i && s == rhs.s; 590 } 591 int i; 592 string s; 593 } 594 595 auto rnd = Random(1337); 596 Gen!Foo gen; 597 assertEqual(gen.gen(rnd), new Foo(0, "")); 598 assertEqual(gen.gen(rnd), new Foo(1, "a")); 599 assertEqual(gen.gen(rnd), new Foo(int.min, "é")); 600 }