1 /** 2 * Module defines routines for generating testing sets. 3 * 4 * To define Arbitrary template for particular type $(B T) (or types) you should follow 5 * compile-time interface: 6 * <ul> 7 * <li>Define $(B generate) function that takes nothing and returns range of $(B T). 8 * This function is used to generate random sets of testing data. Size of required 9 * sample isn't passed thus use lazy ranges to generate possible infinite set of 10 * data.</li> 11 * <li>Define $(B shrink) function that takes value of $(B T) and returns range of 12 * truncated variations. This function is used to reduce failing case data to 13 * minimum possible set. You can return empty array if you like to get large bunch 14 * of random data.</li> 15 * <li>Define $(B specialCases) function that takes nothing and returns range of $(B T). 16 * This function is used to test some special values for particular type like NaN or 17 * null pointer. You can return empty array if no testing on special cases is required.</li> 18 * </ul> 19 * 20 * Usually useful practice is put static assert with $(B CheckArbitrary) template into your implementation 21 * to actually get confidence that you've done it properly. 22 * 23 * Example: 24 * --------- 25 * template Arbitrary(T) 26 * if(isIntegral!T) 27 * { 28 * static assert(CheckArbitrary!T); 29 * 30 * auto generate() 31 * { 32 * return (() => Maybe!T(uniform!"[]"(T.min, T.max))).generator; 33 * } 34 * 35 * auto shrink(T val) 36 * { 37 * class Shrinker 38 * { 39 * T saved; 40 * 41 * this(T firstVal) 42 * { 43 * saved = firstVal; 44 * } 45 * 46 * Maybe!T shrink() 47 * { 48 * if(saved == 0) return Maybe!T.nothing; 49 * 50 * if(saved > 0) saved--; 51 * if(saved < 0) saved++; 52 * 53 * return Maybe!T(saved); 54 * } 55 * } 56 * 57 * return (&(new Shrinker(val)).shrink).generator; 58 * } 59 * 60 * T[] specialCases() 61 * { 62 * return [T.min, 0, T.max]; 63 * } 64 * } 65 * --------- 66 * 67 * Copyright: © 2014 Anton Gushcha 68 * License: Subject to the terms of the MIT license, as written in the included LICENSE file. 69 * Authors: NCrashed <ncrashed@gmail.com> 70 */ 71 module dcheck.arbitrary; 72 73 import std.traits; 74 import std.range; 75 import std.random; 76 import std.conv; 77 import std.math; 78 import dcheck.maybe; 79 import dcheck.generator; 80 81 /// Minimum size of generated arrays (and strings) 82 enum ArrayGenSizeMin = 1; 83 /// Maximum size of generated arrays (and strings) 84 enum ArrayGenSizeMax = 32; 85 86 /** 87 * Checks if $(B T) has Arbitrary template with 88 * $(B generate), $(B shrink) and $(B specialCases) functions. 89 */ 90 template HasArbitrary(T) 91 { 92 template HasGenerate() 93 { 94 static if(__traits(compiles, Arbitrary!T.generate)) 95 { 96 alias ParameterTypeTuple!(Arbitrary!T.generate) Params; 97 alias ReturnType!(Arbitrary!T.generate) RetType; 98 99 enum HasGenerate = 100 isInputRange!RetType && is(ElementType!RetType == T) 101 && Params.length == 0; 102 } else 103 { 104 enum HasGenerate = false; 105 } 106 } 107 108 template HasShrink() 109 { 110 static if(__traits(compiles, Arbitrary!T.shrink )) 111 { 112 alias ParameterTypeTuple!(Arbitrary!T.shrink) Params; 113 alias ReturnType!(Arbitrary!T.shrink) RetType; 114 115 enum HasShrink = 116 isInputRange!RetType 117 && (is(ElementType!RetType == T) || isSomeChar!T && isSomeChar!(ElementType!RetType)) 118 && Params.length == 1 119 && is(Params[0] == T); 120 } else 121 { 122 enum HasShrink = false; 123 } 124 } 125 126 template HasSpecialCases() 127 { 128 static if(__traits(compiles, Arbitrary!T.specialCases)) 129 { 130 alias ParameterTypeTuple!(Arbitrary!T.specialCases) Params; 131 alias ReturnType!(Arbitrary!T.specialCases) RetType; 132 133 enum HasSpecialCases = 134 isInputRange!RetType 135 && (is(ElementType!RetType == T) || isSomeChar!T && isSomeChar!(ElementType!RetType)) 136 && Params.length == 0; 137 } else 138 { 139 enum HasSpecialCases = false; 140 } 141 } 142 143 template isFullDefined() 144 { 145 enum isFullDefined = 146 __traits(compiles, Arbitrary!T) && 147 HasGenerate!() && 148 HasShrink!() && 149 HasSpecialCases!(); 150 } 151 } 152 153 /** 154 * Check the $(B T) type has properly defined $(B Arbitrary) template. Prints useful user-friendly messages 155 * at compile time. 156 * 157 * Good practice is put the template in static assert while defining own instances of $(B Arbitrary) to get 158 * confidence about instance correctness. 159 */ 160 template CheckArbitrary(T) 161 { 162 static assert(__traits(compiles, Arbitrary!T), "Type "~T.stringof~" doesn't have Arbitrary template!"); 163 static assert(HasArbitrary!T.HasGenerate!(), "Type "~T.stringof~" doesn't have generate function in Arbitrary template!"); 164 static assert(HasArbitrary!T.HasShrink!(), "Type "~T.stringof~" doesn't have shrink function in Arbitrary template!"); 165 static assert(HasArbitrary!T.HasSpecialCases!(), "Type "~T.stringof~" doesn't have specialCases function in Arbitrary template!"); 166 enum CheckArbitrary = HasArbitrary!T.isFullDefined!(); 167 } 168 169 /** 170 * Arbitrary for ubyte, byte, ushort, short, uing, int, ulong, long. 171 */ 172 template Arbitrary(T) 173 if(isIntegral!T) 174 { 175 static assert(CheckArbitrary!T); 176 177 auto generate() 178 { 179 return (() => Maybe!T(uniform!"[]"(T.min, T.max))).generator; 180 } 181 182 auto shrink(T val) 183 { 184 class Shrinker 185 { 186 T saved; 187 188 this(T firstVal) 189 { 190 saved = firstVal; 191 } 192 193 Maybe!T shrink() 194 { 195 if(saved == 0) return Maybe!T.nothing; 196 197 saved = saved/2; 198 return Maybe!T(saved); 199 } 200 } 201 202 return (&(new Shrinker(val)).shrink).generator; 203 } 204 205 T[] specialCases() 206 { 207 return [T.min, 0, T.max]; 208 } 209 } 210 unittest 211 { 212 Arbitrary!ubyte.generate; 213 Arbitrary!ubyte.specialCases; 214 assert(Arbitrary!ubyte.shrink(100).take(10).equal([50, 25, 12, 6, 3, 1, 0])); 215 216 Arbitrary!byte.generate; 217 Arbitrary!byte.specialCases; 218 assert(Arbitrary!byte.shrink(100).take(10).equal([50, 25, 12, 6, 3, 1, 0])); 219 assert(Arbitrary!byte.shrink(-100).take(10).equal([-50, -25, -12, -6, -3, -1, 0])); 220 221 Arbitrary!ushort.generate; 222 Arbitrary!ushort.specialCases; 223 assert(Arbitrary!ushort.shrink(100).take(10).equal([50, 25, 12, 6, 3, 1, 0])); 224 225 Arbitrary!short.generate; 226 Arbitrary!short.specialCases; 227 assert(Arbitrary!short.shrink(100).take(10).equal([50, 25, 12, 6, 3, 1, 0])); 228 assert(Arbitrary!short.shrink(-100).take(10).equal([-50, -25, -12, -6, -3, -1, 0])); 229 230 Arbitrary!uint.generate; 231 Arbitrary!uint.specialCases; 232 assert(Arbitrary!ushort.shrink(100).take(10).equal([50, 25, 12, 6, 3, 1, 0])); 233 234 Arbitrary!int.generate; 235 Arbitrary!int.specialCases; 236 assert(Arbitrary!int.shrink(100).take(10).equal([50, 25, 12, 6, 3, 1, 0])); 237 assert(Arbitrary!int.shrink(-100).take(10).equal([-50, -25, -12, -6, -3, -1, 0])); 238 239 Arbitrary!ulong.generate; 240 Arbitrary!ulong.specialCases; 241 assert(Arbitrary!ushort.shrink(100).take(10).equal([50, 25, 12, 6, 3, 1, 0])); 242 243 Arbitrary!long.generate; 244 Arbitrary!long.specialCases; 245 assert(Arbitrary!long.shrink(100).take(10).equal([50, 25, 12, 6, 3, 1, 0])); 246 assert(Arbitrary!long.shrink(-100).take(10).equal([-50, -25, -12, -6, -3, -1, 0])); 247 } 248 249 /** 250 * Arbitrary for float, double 251 */ 252 template Arbitrary(T) 253 if(isFloatingPoint!T) 254 { 255 static assert(CheckArbitrary!T); 256 257 auto generate() 258 { 259 return (() => Maybe!T(uniform!"[]"(-T.max, T.max))).generator; 260 } 261 262 auto shrink(T val) 263 { 264 class Shrinker 265 { 266 T saved; 267 268 this(T firstVal) 269 { 270 saved = firstVal; 271 } 272 273 Maybe!T shrink() 274 { 275 if(approxEqual(saved, 0)) return Maybe!T.nothing; 276 277 saved = saved/2; 278 return Maybe!T(saved); 279 } 280 } 281 282 return (&(new Shrinker(val)).shrink).generator; 283 } 284 285 T[] specialCases() 286 { 287 return [-T.max, 0, T.max, T.nan, T.infinity, T.epsilon, T.min_normal]; 288 } 289 } 290 unittest 291 { 292 import std.math; 293 import std.algorithm; 294 295 Arbitrary!float.generate; 296 Arbitrary!float.specialCases; 297 assert(reduce!"a && approxEqual(b[0], b[1])"(true, Arbitrary!float.shrink(10.0).take(3).zip([5.0, 2.5, 1.25]))); 298 assert(reduce!"a && approxEqual(b[0], b[1])"(true, Arbitrary!float.shrink(-10.0).take(3).zip([-5.0, -2.5, -1.25]))); 299 300 Arbitrary!double.generate; 301 Arbitrary!double.specialCases; 302 assert(reduce!"a && approxEqual(b[0], b[1])"(true, Arbitrary!double.shrink(10.0).take(3).zip([5.0, 2.5, 1.25]))); 303 assert(reduce!"a && approxEqual(b[0], b[1])"(true, Arbitrary!double.shrink(-10.0).take(3).zip([-5.0, -2.5, -1.25]))); 304 } 305 306 /** 307 * Arbitrary for bool 308 */ 309 template Arbitrary(T) 310 if(is(T == bool)) 311 { 312 static assert(CheckArbitrary!T); 313 314 auto generate() 315 { 316 return [true, false]; 317 } 318 319 auto shrink(T val) 320 { 321 return takeNone!(T[]); 322 } 323 324 auto specialCases() 325 { 326 return takeNone!(T[]); 327 } 328 } 329 unittest 330 { 331 assert(Arbitrary!bool.generate.equal([true, false])); 332 assert(Arbitrary!bool.shrink(true).empty); 333 assert(Arbitrary!bool.shrink(false).empty); 334 } 335 336 /** 337 * Arbitrary template for char, dchar, wchar 338 */ 339 template Arbitrary(T) 340 if(isSomeChar!T) 341 { 342 static assert(CheckArbitrary!T); 343 344 auto generate() 345 { 346 return (() => Maybe!T(cast(T)uniform!"[]"('\u0000', '\U0010FFFF'))).generator; 347 } 348 349 auto shrink(T val) 350 { 351 return takeNone!(dchar[]); 352 } 353 354 auto specialCases() 355 { 356 return takeNone!(dchar[]); 357 } 358 } 359 unittest 360 { 361 Arbitrary!char.generate; 362 assert(Arbitrary!char.shrink('a').empty); 363 assert(Arbitrary!char.specialCases().empty); 364 365 Arbitrary!wchar.generate; 366 assert(Arbitrary!wchar.shrink('a').empty); 367 assert(Arbitrary!wchar.specialCases().empty); 368 369 Arbitrary!dchar.generate; 370 assert(Arbitrary!dchar.shrink('a').empty); 371 assert(Arbitrary!dchar.specialCases().empty); 372 } 373 374 /** 375 * Arbitrary template for strings 376 */ 377 template Arbitrary(T) 378 if(isSomeString!T) 379 { 380 static assert(CheckArbitrary!T); 381 382 auto generate() 383 { 384 return (() => Maybe!T(Arbitrary!(Unqual!(ElementType!T)).generate 385 .take(uniform!"[]"(ArrayGenSizeMin, ArrayGenSizeMax)).array.idup.to!T)).generator; 386 } 387 388 auto shrink(T val) 389 { 390 class Shrinker 391 { 392 T saved; 393 394 this(T startVal) 395 { 396 saved = startVal; 397 } 398 399 auto shrink() 400 { 401 if(saved.length > 0) 402 { 403 saved = saved[1..$]; 404 return Maybe!T(saved); 405 } else return Maybe!T.nothing; 406 } 407 } 408 return (&(new Shrinker(val)).shrink).generator; 409 } 410 411 T[] specialCases() 412 { 413 return [cast(T)""]; 414 } 415 } 416 unittest 417 { 418 Arbitrary!string.generate; 419 assert(Arbitrary!string.shrink("a").take(2).equal([""])); 420 assert(Arbitrary!string.shrink("abc").take(3).equal(["bc", "c", ""])); 421 assert(Arbitrary!string.specialCases().equal([""])); 422 423 Arbitrary!wstring.generate; 424 assert(Arbitrary!wstring.shrink("a"w).take(2).equal([""w])); 425 assert(Arbitrary!wstring.shrink("abc"w).take(3).equal(["bc"w, "c"w, ""w])); 426 assert(Arbitrary!wstring.specialCases().equal([""w])); 427 428 Arbitrary!dstring.generate; 429 assert(Arbitrary!dstring.shrink("a"d).take(2).equal([""d])); 430 assert(Arbitrary!dstring.shrink("abc"d).take(3).equal(["bc"d, "c"d, ""d])); 431 assert(Arbitrary!dstring.specialCases().equal([""d])); 432 } 433 434 /** 435 * Arbitrary template for char, dchar, wchar 436 */ 437 template Arbitrary(T) 438 if(isArray!T && HasArbitrary!(ElementType!T).HasGenerate!() && !isSomeString!T) 439 { 440 static assert(CheckArbitrary!T); 441 442 auto generate() 443 { 444 return (() => Maybe!T( Arbitrary!(ElementType!T).generate.take(uniform!"[]"(ArrayGenSizeMin,ArrayGenSizeMax)).array )).generator; 445 } 446 447 auto shrink(T val) 448 { 449 class Shrinker 450 { 451 T saved; 452 453 this(T startVal) 454 { 455 saved = startVal; 456 } 457 458 auto shrink() 459 { 460 if(saved.length > 0) 461 { 462 saved = saved[1..$]; 463 return Maybe!T(saved); 464 } else return Maybe!T.nothing; 465 } 466 } 467 return (&(new Shrinker(val)).shrink).generator; 468 } 469 470 auto specialCases() 471 { 472 return [cast(T)[]]; 473 } 474 } 475 unittest 476 { 477 Arbitrary!(uint[]).generate; 478 assert(Arbitrary!(uint[]).shrink([1u, 2u, 3u]).take(3).equal([[2u, 3u], [3u], []])); 479 assert(Arbitrary!(uint[]).specialCases().equal([cast(uint[])[]])); 480 }