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 }