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