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 }