1 module unit_threaded.reflection; 2 3 import unit_threaded.from; 4 import std.traits; // can't find out why 5 6 /** 7 * Common data for test functions and test classes 8 */ 9 alias void delegate() TestFunction; 10 struct TestData { 11 string name; 12 TestFunction testFunction; ///only used for functions, null for classes 13 bool hidden; 14 bool shouldFail; 15 bool singleThreaded; 16 bool builtin; 17 string suffix; // append to end of getPath 18 string[] tags; 19 TypeInfo exceptionTypeInfo; // for ShouldFailWith 20 int flakyRetries = 0; 21 22 string getPath() const pure nothrow { 23 string path = name.dup; 24 import std.array: empty; 25 if(!suffix.empty) path ~= "." ~ suffix; 26 return path; 27 } 28 29 bool isTestClass() @safe const pure nothrow { 30 return testFunction is null; 31 } 32 } 33 34 35 /** 36 * Finds all test cases (functions, classes, built-in unittest blocks) 37 * Template parameters are module strings 38 */ 39 const(TestData)[] allTestData(MOD_STRINGS...)() 40 if(from!"std.meta".allSatisfy!(from!"std.traits".isSomeString, typeof(MOD_STRINGS))) 41 { 42 import std.array: join; 43 import std.range : iota; 44 import std.format : format; 45 import std.algorithm : map; 46 47 string getModulesString() { 48 string[] modules; 49 foreach(i, module_; MOD_STRINGS) modules ~= "module%d = %s".format(i, module_); 50 return modules.join(", "); 51 } 52 53 enum modulesString = getModulesString; 54 mixin("import " ~ modulesString ~ ";"); 55 mixin("return allTestData!(" ~ 56 MOD_STRINGS.length.iota.map!(i => "module%d".format(i)).join(", ") ~ 57 ");"); 58 } 59 60 61 /** 62 * Finds all test cases (functions, classes, built-in unittest blocks) 63 * Template parameters are module symbols 64 */ 65 const(TestData)[] allTestData(MOD_SYMBOLS...)() 66 if(!from!"std.meta".anySatisfy!(from!"std.traits".isSomeString, typeof(MOD_SYMBOLS))) 67 { 68 auto allTestsWithFunc(string expr)() pure { 69 import std.traits: ReturnType; 70 import std.meta: AliasSeq; 71 //tests is whatever type expr returns 72 ReturnType!(mixin(expr ~ q{!(MOD_SYMBOLS[0])})) tests; 73 foreach(module_; AliasSeq!MOD_SYMBOLS) { 74 tests ~= mixin(expr ~ q{!module_()}); //e.g. tests ~= moduleTestClasses!module_ 75 } 76 return tests; 77 } 78 79 return allTestsWithFunc!"moduleTestClasses" ~ 80 allTestsWithFunc!"moduleTestFunctions" ~ 81 allTestsWithFunc!"moduleUnitTests"; 82 } 83 84 85 private template Identity(T...) if(T.length > 0) { 86 static if(__traits(compiles, { alias x = T[0]; })) 87 alias Identity = T[0]; 88 else 89 enum Identity = T[0]; 90 } 91 92 93 /** 94 * Finds all built-in unittest blocks in the given module. 95 * Recurses into structs, classes, and unions of the module. 96 * 97 * @return An array of TestData structs 98 */ 99 TestData[] moduleUnitTests(alias module_)() pure nothrow { 100 101 // Return a name for a unittest block. If no @Name UDA is found a name is 102 // created automatically, else the UDA is used. 103 // the weird name for the first template parameter is so that it doesn't clash 104 // with a package name 105 string unittestName(alias _theUnitTest, int index)() @safe nothrow { 106 import std.conv: text, to; 107 import std.traits: fullyQualifiedName; 108 import std.traits: getUDAs; 109 import std.meta: Filter; 110 import unit_threaded.attrs: Name; 111 112 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 113 114 enum nameAttrs = getUDAs!(_theUnitTest, Name); 115 static assert(nameAttrs.length == 0 || nameAttrs.length == 1, "Found multiple Name UDAs on unittest"); 116 117 enum strAttrs = Filter!(isStringUDA, __traits(getAttributes, _theUnitTest)); 118 enum hasName = nameAttrs.length || strAttrs.length == 1; 119 enum prefix = fullyQualifiedName!(__traits(parent, _theUnitTest)) ~ "."; 120 121 static if(hasName) { 122 static if(nameAttrs.length == 1) 123 return prefix ~ nameAttrs[0].value; 124 else 125 return prefix ~ strAttrs[0]; 126 } else { 127 string name; 128 try { 129 return prefix ~ "unittest" ~ (index).to!string; 130 } catch(Exception) { 131 assert(false, text("Error converting ", index, " to string")); 132 } 133 } 134 } 135 136 void function() getUDAFunction(alias composite, alias uda)() pure nothrow { 137 import std.traits: fullyQualifiedName, moduleName, isSomeFunction, hasUDA; 138 139 // Due to: 140 // https://issues.dlang.org/show_bug.cgi?id=17441 141 // moduleName!composite might fail, so we try to import that only if 142 // if compiles, then try again with fullyQualifiedName 143 enum moduleNameStr = `import ` ~ moduleName!composite ~ `;`; 144 enum fullyQualifiedStr = `import ` ~ fullyQualifiedName!composite ~ `;`; 145 146 static if(__traits(compiles, mixin(moduleNameStr))) 147 mixin(moduleNameStr); 148 else static if(__traits(compiles, mixin(fullyQualifiedStr))) 149 mixin(fullyQualifiedStr); 150 151 void function()[] ret; 152 foreach(memberStr; __traits(allMembers, composite)) { 153 static if(__traits(compiles, Identity!(__traits(getMember, composite, memberStr)))) { 154 alias member = Identity!(__traits(getMember, composite, memberStr)); 155 static if(__traits(compiles, &member)) { 156 static if(isSomeFunction!member && hasUDA!(member, uda)) { 157 ret ~= &member; 158 } 159 } 160 } 161 } 162 163 return ret.length ? ret[0] : null; 164 } 165 166 TestData[] testData; 167 168 void addMemberUnittests(alias composite)() pure nothrow { 169 170 import unit_threaded.attrs; 171 import unit_threaded.uda: hasUtUDA; 172 import std.traits: hasUDA; 173 import std.meta: Filter, aliasSeqOf; 174 175 foreach(index, eLtEstO; __traits(getUnitTests, composite)) { 176 177 enum dontTest = hasUDA!(eLtEstO, DontTest); 178 179 static if(!dontTest) { 180 181 enum name = unittestName!(eLtEstO, index); 182 enum hidden = hasUDA!(eLtEstO, HiddenTest); 183 enum shouldFail = hasUDA!(eLtEstO, ShouldFail) || hasUtUDA!(eLtEstO, ShouldFailWith); 184 enum singleThreaded = hasUDA!(eLtEstO, Serial); 185 enum builtin = true; 186 enum suffix = ""; 187 188 // let's check for @Values UDAs, which are actually of type ValuesImpl 189 enum isValues(alias T) = is(typeof(T)) && is(typeof(T):ValuesImpl!U, U); 190 alias valuesUDAs = Filter!(isValues, __traits(getAttributes, eLtEstO)); 191 192 enum isTags(alias T) = is(typeof(T)) && is(typeof(T) == Tags); 193 enum tags = tagsFromAttrs!(Filter!(isTags, __traits(getAttributes, eLtEstO))); 194 enum exceptionTypeInfo = getExceptionTypeInfo!eLtEstO; 195 enum flakyRetries = getFlakyRetries!(eLtEstO); 196 197 static if(valuesUDAs.length == 0) { 198 testData ~= TestData(name, 199 () { 200 auto setup = getUDAFunction!(composite, Setup); 201 auto shutdown = getUDAFunction!(composite, Shutdown); 202 203 if(setup) setup(); 204 scope(exit) if(shutdown) shutdown(); 205 206 eLtEstO(); 207 }, 208 hidden, 209 shouldFail, 210 singleThreaded, 211 builtin, 212 suffix, 213 tags, 214 exceptionTypeInfo, 215 flakyRetries); 216 } else { 217 import std.range; 218 219 // cartesianProduct doesn't work with only one range, so in the usual case 220 // of only one @Values UDA, we bind to prod with a range of tuples, just 221 // as returned by cartesianProduct. 222 223 static if(valuesUDAs.length == 1) { 224 import std.typecons; 225 enum prod = valuesUDAs[0].values.map!(a => tuple(a)); 226 } else { 227 mixin(`enum prod = cartesianProduct(` ~ valuesUDAs.length.iota.map! 228 (a => `valuesUDAs[` ~ guaranteedToString(a) ~ `].values`).join(", ") ~ `);`); 229 } 230 231 foreach(comb; aliasSeqOf!prod) { 232 enum valuesName = valuesName(comb); 233 234 static if(hasUDA!(eLtEstO, AutoTags)) 235 enum realTags = tags ~ valuesName.split(".").array; 236 else 237 enum realTags = tags; 238 239 testData ~= TestData(name ~ "." ~ valuesName, 240 () { 241 foreach(i; aliasSeqOf!(comb.length.iota)) 242 ValueHolder!(typeof(comb[i])).values[i] = comb[i]; 243 eLtEstO(); 244 }, 245 hidden, 246 shouldFail, 247 singleThreaded, 248 builtin, 249 suffix, 250 realTags, 251 exceptionTypeInfo, 252 flakyRetries); 253 } 254 } 255 } 256 } 257 } 258 259 260 // Keeps track of mangled names of everything visited. 261 bool[string] visitedMembers; 262 263 void addUnitTestsRecursively(alias composite)() pure nothrow { 264 import std.traits: fullyQualifiedName; 265 266 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 267 268 if (composite.mangleof in visitedMembers) 269 return; 270 visitedMembers[composite.mangleof] = true; 271 addMemberUnittests!composite(); 272 foreach(member; __traits(allMembers, composite)){ 273 enum notPrivate = __traits(compiles, mixin(member)); //only way I know to check if private 274 static if ( 275 notPrivate && 276 // If visibility of the member is deprecated, the next line still returns true 277 // and yet spills deprecation warning. If deprecation is turned into error, 278 // all works as intended. 279 __traits(compiles, __traits(getMember, composite, member)) && 280 __traits(compiles, __traits(allMembers, __traits(getMember, composite, member))) && 281 __traits(compiles, recurse!(__traits(getMember, composite, member))) 282 ) { 283 recurse!(__traits(getMember, composite, member)); 284 } 285 } 286 } 287 288 void recurse(child)() pure nothrow { 289 enum notPrivate = __traits(compiles, child.init); //only way I know to check if private 290 static if (is(child == class) || is(child == struct) || is(child == union)) { 291 addUnitTestsRecursively!child; 292 } 293 } 294 295 addUnitTestsRecursively!module_(); 296 return testData; 297 } 298 299 private TypeInfo getExceptionTypeInfo(alias Test)() { 300 import unit_threaded.should: UnitTestException; 301 import unit_threaded.uda: hasUtUDA, getUtUDAs; 302 import unit_threaded.attrs: ShouldFailWith; 303 304 static if(hasUtUDA!(Test, ShouldFailWith)) { 305 alias uda = getUtUDAs!(Test, ShouldFailWith)[0]; 306 return typeid(uda.Type); 307 } else 308 return null; 309 } 310 311 312 private string valuesName(T)(T tuple) { 313 import std.range: iota; 314 import std.meta: aliasSeqOf; 315 316 string[] parts; 317 foreach(a; aliasSeqOf!(tuple.length.iota)) 318 parts ~= guaranteedToString(tuple[a]); 319 return parts.join("."); 320 } 321 322 private string guaranteedToString(T)(T value) nothrow pure @safe { 323 import std.conv; 324 try 325 return value.to!string; 326 catch(Exception ex) 327 assert(0, "Could not convert value to string"); 328 } 329 330 private string getValueAsString(T)(T value) nothrow pure @safe { 331 import std.conv; 332 try 333 return value.to!string; 334 catch(Exception ex) 335 assert(0, "Could not convert value to string"); 336 } 337 338 339 private template isStringUDA(alias T) { 340 import std.traits: isSomeString; 341 static if(__traits(compiles, isSomeString!(typeof(T)))) 342 enum isStringUDA = isSomeString!(typeof(T)); 343 else 344 enum isStringUDA = false; 345 } 346 347 unittest { 348 static assert(isStringUDA!"foo"); 349 static assert(!isStringUDA!5); 350 } 351 352 private template isPrivate(alias module_, string moduleMember) { 353 import unit_threaded.uda: HasTypes; 354 import std.traits: fullyQualifiedName; 355 356 // obfuscate the name (user code might just be defining their own isPrivate) 357 mixin(`import ` ~ fullyQualifiedName!module_ ~ `: ut_mmbr__ = ` ~ moduleMember ~ `;`); 358 static if(__traits(compiles, isSomeFunction!(ut_mmbr__))) { 359 static if(__traits(compiles, &ut_mmbr__)) 360 enum isPrivate = false; 361 else static if(__traits(compiles, new ut_mmbr__)) 362 enum isPrivate = false; 363 else static if(__traits(compiles, HasTypes!ut_mmbr__)) 364 enum isPrivate = !HasTypes!ut_mmbr__; 365 else 366 enum isPrivate = true; 367 } else { 368 enum isPrivate = true; 369 } 370 } 371 372 373 // if this member is a test function or class, given the predicate 374 private template PassesTestPred(alias module_, alias pred, string moduleMember) { 375 import std.traits: fullyQualifiedName; 376 import unit_threaded.meta: importMember; 377 import unit_threaded.uda: HasAttribute; 378 import unit_threaded.attrs: DontTest; 379 380 //should be the line below instead but a compiler bug prevents it 381 //mixin(importMember!module_(moduleMember)); 382 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); 383 alias I(T...) = T; 384 static if(!__traits(compiles, I!(__traits(getMember, module_, moduleMember)))) { 385 enum PassesTestPred = false; 386 } else { 387 alias member = I!(__traits(getMember, module_, moduleMember)); 388 389 template canCheckIfSomeFunction(T...) { 390 enum canCheckIfSomeFunction = T.length == 1 && __traits(compiles, isSomeFunction!(T[0])); 391 } 392 393 private string funcCallMixin(alias T)() { 394 import std.conv: to; 395 string[] args; 396 foreach(i, ParamType; Parameters!T) { 397 args ~= `arg` ~ i.to!string; 398 } 399 400 return moduleMember ~ `(` ~ args.join(`,`) ~ `);`; 401 } 402 403 private string argsMixin(alias T)() { 404 import std.conv: to; 405 string[] args; 406 foreach(i, ParamType; Parameters!T) { 407 args ~= ParamType.stringof ~ ` arg` ~ i.to!string ~ `;`; 408 } 409 410 return args.join("\n"); 411 } 412 413 template canCallMember() { 414 void _f() { 415 mixin(argsMixin!member); 416 mixin(funcCallMixin!member); 417 } 418 } 419 420 template canInstantiate() { 421 void _f() { 422 mixin(`auto _ = new ` ~ moduleMember ~ `;`); 423 } 424 } 425 426 template isPrivate() { 427 static if(!canCheckIfSomeFunction!member) { 428 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember)); 429 } else { 430 static if(isSomeFunction!member) { 431 enum isPrivate = !__traits(compiles, canCallMember!()); 432 } else static if(is(member)) { 433 static if(isAggregateType!member) 434 enum isPrivate = !__traits(compiles, canInstantiate!()); 435 else 436 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember)); 437 } else { 438 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember)); 439 } 440 } 441 } 442 443 enum notPrivate = !isPrivate!(); 444 enum PassesTestPred = !isPrivate!() && pred!(module_, moduleMember) && 445 !HasAttribute!(module_, moduleMember, DontTest); 446 } 447 } 448 449 450 /** 451 * Finds all test classes (classes implementing a test() function) 452 * in the given module 453 */ 454 TestData[] moduleTestClasses(alias module_)() pure nothrow { 455 456 template isTestClass(alias module_, string moduleMember) { 457 import unit_threaded.meta: importMember; 458 import unit_threaded.uda: HasAttribute; 459 import unit_threaded.attrs: UnitTest; 460 import std.traits: isAggregateType; 461 462 mixin(importMember!module_(moduleMember)); 463 464 alias member = Identity!(mixin(moduleMember)); 465 466 static if(.isPrivate!(module_, moduleMember)) { 467 enum isTestClass = false; 468 } else static if(!__traits(compiles, isAggregateType!(member))) { 469 enum isTestClass = false; 470 } else static if(!isAggregateType!(member)) { 471 enum isTestClass = false; 472 } else static if(!__traits(compiles, { return new member; })) { 473 enum isTestClass = false; //can't new it, can't use it 474 } else { 475 enum hasUnitTest = HasAttribute!(module_, moduleMember, UnitTest); 476 enum hasTestMethod = __traits(hasMember, member, "test"); 477 478 enum isTestClass = is(member == class) && (hasTestMethod || hasUnitTest); 479 } 480 } 481 482 return moduleTestData!(module_, isTestClass, memberTestData); 483 } 484 485 486 /** 487 * Finds all test functions in the given module. 488 * Returns an array of TestData structs 489 */ 490 TestData[] moduleTestFunctions(alias module_)() pure { 491 492 import unit_threaded.uda: isTypesAttr; 493 494 template isTestFunction(alias module_, string moduleMember) { 495 import unit_threaded.meta: importMember; 496 import unit_threaded.attrs: UnitTest; 497 import unit_threaded.uda: HasAttribute, GetTypes; 498 import std.meta: AliasSeq; 499 import std.traits: isSomeFunction; 500 501 mixin(importMember!module_(moduleMember)); 502 503 static if(.isPrivate!(module_, moduleMember)) { 504 enum isTestFunction = false; 505 } else static if(AliasSeq!(mixin(moduleMember)).length != 1) { 506 enum isTestFunction = false; 507 } else static if(isSomeFunction!(mixin(moduleMember))) { 508 enum isTestFunction = hasTestPrefix!(module_, moduleMember) || 509 HasAttribute!(module_, moduleMember, UnitTest); 510 } else static if(__traits(compiles, __traits(getAttributes, mixin(moduleMember)))) { 511 // in this case we handle the possibility of a template function with 512 // the @Types UDA attached to it 513 alias types = GetTypes!(mixin(moduleMember)); 514 enum isTestFunction = hasTestPrefix!(module_, moduleMember) && 515 types.length > 0; 516 } else { 517 enum isTestFunction = false; 518 } 519 520 } 521 522 template hasTestPrefix(alias module_, string member) { 523 import std.uni: isUpper; 524 import unit_threaded.meta: importMember; 525 526 mixin(importMember!module_(member)); 527 528 enum prefix = "test"; 529 enum minSize = prefix.length + 1; 530 531 static if(member.length >= minSize && member[0 .. prefix.length] == prefix && 532 isUpper(member[prefix.length])) { 533 enum hasTestPrefix = true; 534 } else { 535 enum hasTestPrefix = false; 536 } 537 } 538 539 return moduleTestData!(module_, isTestFunction, createFuncTestData); 540 } 541 542 private TestData[] createFuncTestData(alias module_, string moduleMember)() { 543 import unit_threaded.meta: importMember; 544 import unit_threaded.uda: GetAttributes, HasAttribute, GetTypes, HasTypes; 545 import unit_threaded.attrs; 546 import std.meta: aliasSeqOf; 547 548 mixin(importMember!module_(moduleMember)); 549 /* 550 Get all the test functions for this module member. There might be more than one 551 when using parametrized unit tests. 552 553 Examples: 554 ------ 555 void testFoo() {} // -> the array contains one element, testFoo 556 @(1, 2, 3) void testBar(int) {} // The array contains 3 elements, one for each UDA value 557 @Types!(int, float) void testBaz(T)() {} //The array contains 2 elements, one for each type 558 ------ 559 */ 560 // if the predicate returned true (which is always the case here), then it's either 561 // a regular function or a templated one. If regular we can get a pointer to it 562 enum isRegularFunction = __traits(compiles, &__traits(getMember, module_, moduleMember)); 563 564 static if(isRegularFunction) { 565 566 enum func = &__traits(getMember, module_, moduleMember); 567 enum arity = arity!func; 568 569 static if(arity == 0) 570 // the reason we're creating a lambda to call the function is that test functions 571 // are ordinary functions, but we're storing delegates 572 return [ memberTestData!(module_, moduleMember)(() { func(); }) ]; //simple case, just call the function 573 else { 574 575 // the function has parameters, check if it has UDAs for value parameters to be passed to it 576 alias params = Parameters!func; 577 578 import std.range: iota; 579 import std.algorithm: any; 580 import std.typecons: tuple, Tuple; 581 582 bool hasAttributesForAllParams() { 583 auto ret = true; 584 foreach(p; params) { 585 if(tuple(GetAttributes!(module_, moduleMember, p)).length == 0) { 586 ret = false; 587 } 588 } 589 return ret; 590 } 591 592 static if(!hasAttributesForAllParams) { 593 import std.conv: text; 594 pragma(msg, text("Warning: ", moduleMember, " passes the criteria for a value-parameterized test function", 595 " but doesn't have the appropriate value UDAs.\n", 596 " Consider changing its name or annotating it with @DontTest")); 597 return []; 598 } else { 599 600 static if(arity == 1) { 601 // bind a range of tuples to prod just as cartesianProduct returns 602 enum prod = [GetAttributes!(module_, moduleMember, params[0])].map!(a => tuple(a)); 603 } else { 604 import std.conv: text; 605 606 mixin(`enum prod = cartesianProduct(` ~ params.length.iota.map! 607 (a => `[GetAttributes!(module_, moduleMember, params[` ~ guaranteedToString(a) ~ `])]`).join(", ") ~ `);`); 608 } 609 610 TestData[] testData; 611 foreach(comb; aliasSeqOf!prod) { 612 enum valuesName = valuesName(comb); 613 614 static if(HasAttribute!(module_, moduleMember, AutoTags)) 615 enum extraTags = valuesName.split(".").array; 616 else 617 enum string[] extraTags = []; 618 619 620 testData ~= memberTestData!(module_, moduleMember, extraTags)( 621 // func(value0, value1, ...) 622 () { func(comb.expand); }, 623 valuesName); 624 } 625 626 return testData; 627 } 628 } 629 } else static if(HasTypes!(mixin(moduleMember))) { //template function with @Types 630 alias types = GetTypes!(mixin(moduleMember)); 631 TestData[] testData; 632 foreach(type; types) { 633 634 static if(HasAttribute!(module_, moduleMember, AutoTags)) 635 enum extraTags = [type.stringof]; 636 else 637 enum string[] extraTags = []; 638 639 alias member = Identity!(mixin(moduleMember)); 640 641 testData ~= memberTestData!(module_, moduleMember, extraTags)( 642 () { member!type(); }, 643 type.stringof); 644 } 645 return testData; 646 } else { 647 return []; 648 } 649 } 650 651 652 653 // this funtion returns TestData for either classes or test functions 654 // built-in unittest modules are handled by moduleUnitTests 655 // pred determines what qualifies as a test 656 // createTestData must return TestData[] 657 private TestData[] moduleTestData(alias module_, alias pred, alias createTestData)() pure { 658 import std.traits: fullyQualifiedName; 659 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 660 661 TestData[] testData; 662 663 foreach(moduleMember; __traits(allMembers, module_)) { 664 665 static if(PassesTestPred!(module_, pred, moduleMember)) 666 testData ~= createTestData!(module_, moduleMember); 667 } 668 669 return testData; 670 671 } 672 673 // TestData for a member of a module (either a test function or test class) 674 private TestData memberTestData(alias module_, string moduleMember, string[] extraTags = []) 675 (TestFunction testFunction = null, string suffix = "") { 676 677 import unit_threaded.uda: HasAttribute, GetAttributes, hasUtUDA; 678 import unit_threaded.attrs; 679 import std.traits: fullyQualifiedName; 680 681 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 682 683 immutable singleThreaded = HasAttribute!(module_, moduleMember, Serial); 684 enum builtin = false; 685 enum tags = tagsFromAttrs!(GetAttributes!(module_, moduleMember, Tags)); 686 enum exceptionTypeInfo = getExceptionTypeInfo!(mixin(moduleMember)); 687 enum shouldFail = 688 HasAttribute!(module_, moduleMember, ShouldFail) || 689 hasUtUDA!(mixin(moduleMember), ShouldFailWith); 690 enum flakyRetries = getFlakyRetries!(mixin(moduleMember)); 691 692 return TestData(fullyQualifiedName!module_~ "." ~ moduleMember, 693 testFunction, 694 HasAttribute!(module_, moduleMember, HiddenTest), 695 shouldFail, 696 singleThreaded, 697 builtin, 698 suffix, 699 tags ~ extraTags, 700 exceptionTypeInfo, 701 flakyRetries); 702 } 703 704 private int getFlakyRetries(alias test)() { 705 import unit_threaded.attrs: Flaky; 706 import std.traits: getUDAs; 707 import std.conv: text; 708 709 alias flakies = getUDAs!(test, Flaky); 710 711 static assert(flakies.length == 0 || flakies.length == 1, 712 text("Only 1 @Flaky allowed, found ", flakies.length, " on ", 713 __traits(identifier, test))); 714 715 static if(flakies.length == 1) { 716 static if(is(flakies[0])) 717 return Flaky.defaultRetries; 718 else 719 return flakies[0].retries; 720 } else 721 return 0; 722 } 723 724 string[] tagsFromAttrs(T...)() { 725 static assert(T.length <= 1, "@Tags can only be applied once"); 726 static if(T.length) 727 return T[0].values; 728 else 729 return []; 730 } 731 732 version(unittest) { 733 734 import unit_threaded.tests.module_with_tests; //defines tests and non-tests 735 import unit_threaded.asserts; 736 import std.algorithm; 737 import std.array; 738 739 //helper function for the unittest blocks below 740 private auto addModPrefix(string[] elements, 741 string module_ = "unit_threaded.tests.module_with_tests") nothrow { 742 return elements.map!(a => module_ ~ "." ~ a).array; 743 } 744 } 745 746 unittest { 747 const expected = addModPrefix([ "FooTest", "BarTest", "Blergh", "Issue83"]); 748 const actual = moduleTestClasses!(unit_threaded.tests.module_with_tests). 749 map!(a => a.name).array; 750 assertEqual(actual, expected); 751 } 752 753 unittest { 754 const expected = addModPrefix([ "testFoo", "testBar", "funcThatShouldShowUpCosOfAttr"]); 755 const actual = moduleTestFunctions!(unit_threaded.tests.module_with_tests). 756 map!(a => a.getPath).array; 757 assertEqual(actual, expected); 758 } 759 760 761 unittest { 762 const expected = addModPrefix(["unittest0", "unittest1", "myUnitTest", 763 "StructWithUnitTests.InStruct", "StructWithUnitTests.unittest1"]); 764 const actual = moduleUnitTests!(unit_threaded.tests.module_with_tests). 765 map!(a => a.name).array; 766 assertEqual(actual, expected); 767 } 768 769 version(unittest) { 770 import unit_threaded.testcase: TestCase; 771 private void assertFail(TestCase test, string file = __FILE__, size_t line = __LINE__) { 772 import core.exception; 773 import std.conv; 774 775 test.silence; 776 assert(test() != [], 777 file ~ ":" ~ line.to!string ~ " Expected test case " ~ test.getPath ~ 778 " to fail but it didn't"); 779 } 780 781 private void assertPass(TestCase test, string file = __FILE__, size_t line = __LINE__) { 782 import unit_threaded.should: fail; 783 if(test() != []) 784 fail("'" ~ test.getPath ~ "' was expected to pass but failed", file, line); 785 } 786 } 787 788 @("Test that parametrized value tests work") 789 unittest { 790 import unit_threaded.factory; 791 import unit_threaded.testcase; 792 import unit_threaded.tests.parametrized; 793 794 const testData = allTestData!(unit_threaded.tests.parametrized). 795 filter!(a => a.name.endsWith("testValues")).array; 796 797 auto tests = createTestCases(testData); 798 assertEqual(tests.length, 3); 799 800 // the first and third test should pass, the second should fail 801 assertPass(tests[0]); 802 assertPass(tests[2]); 803 804 assertFail(tests[1]); 805 } 806 807 808 @("Test that parametrized type tests work") 809 unittest { 810 import unit_threaded.factory; 811 import unit_threaded.testcase; 812 import unit_threaded.tests.parametrized; 813 814 const testData = allTestData!(unit_threaded.tests.parametrized). 815 filter!(a => a.name.endsWith("testTypes")).array; 816 const expected = addModPrefix(["testTypes.float", "testTypes.int"], 817 "unit_threaded.tests.parametrized"); 818 const actual = testData.map!(a => a.getPath).array; 819 assertEqual(actual, expected); 820 821 auto tests = createTestCases(testData); 822 assertEqual(tests.map!(a => a.getPath).array, expected); 823 824 assertPass(tests[1]); 825 assertFail(tests[0]); 826 } 827 828 @("Value parametrized built-in unittests") 829 unittest { 830 import unit_threaded.factory; 831 import unit_threaded.testcase; 832 import unit_threaded.tests.parametrized; 833 834 const testData = allTestData!(unit_threaded.tests.parametrized). 835 filter!(a => a.name.canFind("builtinIntValues")).array; 836 837 auto tests = createTestCases(testData); 838 assertEqual(tests.length, 4); 839 840 // these should be ok 841 assertPass(tests[1]); 842 843 //these should fail 844 assertFail(tests[0]); 845 assertFail(tests[2]); 846 assertFail(tests[3]); 847 } 848 849 850 @("Tests can be selected by tags") unittest { 851 import unit_threaded.factory; 852 import unit_threaded.testcase; 853 import unit_threaded.tests.tags; 854 855 const testData = allTestData!(unit_threaded.tests.tags).array; 856 auto testsNoTags = createTestCases(testData); 857 assertEqual(testsNoTags.length, 4); 858 assertPass(testsNoTags[0]); 859 assertFail(testsNoTags.find!(a => a.getPath.canFind("unittest1")).front); 860 assertFail(testsNoTags[2]); 861 assertFail(testsNoTags[3]); 862 863 auto testsNinja = createTestCases(testData, ["@ninja"]); 864 assertEqual(testsNinja.length, 1); 865 assertPass(testsNinja[0]); 866 867 auto testsMake = createTestCases(testData, ["@make"]); 868 assertEqual(testsMake.length, 3); 869 assertPass(testsMake.find!(a => a.getPath.canFind("testMake")).front); 870 assertPass(testsMake.find!(a => a.getPath.canFind("unittest0")).front); 871 assertFail(testsMake.find!(a => a.getPath.canFind("unittest2")).front); 872 873 auto testsNotNinja = createTestCases(testData, ["~@ninja"]); 874 assertEqual(testsNotNinja.length, 3); 875 assertPass(testsNotNinja.find!(a => a.getPath.canFind("testMake")).front); 876 assertFail(testsNotNinja.find!(a => a.getPath.canFind("unittest1")).front); 877 assertFail(testsNotNinja.find!(a => a.getPath.canFind("unittest2")).front); 878 879 assertEqual(createTestCases(testData, ["unit_threaded.tests.tags.testMake", "@ninja"]).length, 0); 880 } 881 882 @("Parametrized built-in tests with @AutoTags get tagged by value") 883 unittest { 884 import unit_threaded.factory; 885 import unit_threaded.testcase; 886 import unit_threaded.tests.parametrized; 887 888 const testData = allTestData!(unit_threaded.tests.parametrized). 889 filter!(a => a.name.canFind("builtinIntValues")).array; 890 891 auto two = createTestCases(testData, ["@2"]); 892 893 assertEqual(two.length, 1); 894 assertFail(two[0]); 895 896 auto three = createTestCases(testData, ["@3"]); 897 assertEqual(three.length, 1); 898 assertPass(three[0]); 899 } 900 901 @("Value parametrized function tests with @AutoTags get tagged by value") 902 unittest { 903 import unit_threaded.factory; 904 import unit_threaded.testcase; 905 import unit_threaded.tests.parametrized; 906 907 const testData = allTestData!(unit_threaded.tests.parametrized). 908 filter!(a => a.name.canFind("testValues")).array; 909 910 auto two = createTestCases(testData, ["@2"]); 911 assertEqual(two.length, 1); 912 assertFail(two[0]); 913 } 914 915 @("Type parameterized tests with @AutoTags get tagged by type") 916 unittest { 917 import unit_threaded.factory; 918 import unit_threaded.testcase; 919 import unit_threaded.tests.parametrized; 920 921 const testData = allTestData!(unit_threaded.tests.parametrized). 922 filter!(a => a.name.canFind("testTypes")).array; 923 924 auto tests = createTestCases(testData, ["@int"]); 925 assertEqual(tests.length, 1); 926 assertPass(tests[0]); 927 } 928 929 @("Cartesian parameterized built-in values") unittest { 930 import unit_threaded.factory; 931 import unit_threaded.testcase; 932 import unit_threaded.should: shouldBeSameSetAs; 933 import unit_threaded.tests.parametrized; 934 import unit_threaded.attrs: getValue; 935 936 const testData = allTestData!(unit_threaded.tests.parametrized). 937 filter!(a => a.name.canFind("cartesianBuiltinNoAutoTags")).array; 938 939 auto tests = createTestCases(testData); 940 tests.map!(a => a.getPath).array.shouldBeSameSetAs( 941 addModPrefix(["foo.red", "foo.blue", "foo.green", "bar.red", "bar.blue", "bar.green"]. 942 map!(a => "cartesianBuiltinNoAutoTags." ~ a).array, 943 "unit_threaded.tests.parametrized")); 944 assertEqual(tests.length, 6); 945 946 auto fooRed = tests.find!(a => a.getPath.canFind("foo.red")).front; 947 assertPass(fooRed); 948 assertEqual(getValue!(string, 0), "foo"); 949 assertEqual(getValue!(string, 1), "red"); 950 assertEqual(testData.find!(a => a.getPath.canFind("foo.red")).front.tags, []); 951 952 auto barGreen = tests.find!(a => a.getPath.canFind("bar.green")).front; 953 assertFail(barGreen); 954 assertEqual(getValue!(string, 0), "bar"); 955 assertEqual(getValue!(string, 1), "green"); 956 957 assertEqual(testData.find!(a => a.getPath.canFind("bar.green")).front.tags, []); 958 assertEqual(allTestData!(unit_threaded.tests.parametrized). 959 filter!(a => a.name.canFind("cartesianBuiltinAutoTags")).array. 960 find!(a => a.getPath.canFind("bar.green")).front.tags, 961 ["bar", "green"]); 962 } 963 964 @("Cartesian parameterized function values") unittest { 965 import unit_threaded.factory; 966 import unit_threaded.testcase; 967 import unit_threaded.should: shouldBeSameSetAs; 968 969 const testData = allTestData!(unit_threaded.tests.parametrized). 970 filter!(a => a.name.canFind("CartesianFunction")).array; 971 972 auto tests = createTestCases(testData); 973 tests.map!(a => a.getPath).array.shouldBeSameSetAs( 974 addModPrefix(["1.foo", "1.bar", "2.foo", "2.bar", "3.foo", "3.bar"]. 975 map!(a => "testCartesianFunction." ~ a).array, 976 "unit_threaded.tests.parametrized")); 977 978 foreach(test; tests) { 979 test.getPath.canFind("2.bar") 980 ? assertPass(test) 981 : assertFail(test); 982 } 983 984 assertEqual(testData.find!(a => a.getPath.canFind("2.bar")).front.tags, 985 ["2", "bar"]); 986 987 } 988 989 @("module setup and shutdown") 990 unittest { 991 import unit_threaded.testcase; 992 import unit_threaded.factory; 993 import unit_threaded.tests.module_with_setup: gNumBefore, gNumAfter; 994 995 const testData = allTestData!"unit_threaded.tests.module_with_setup".array; 996 auto tests = createTestCases(testData); 997 assertEqual(tests.length, 2); 998 999 assertPass(tests[0]); 1000 assertEqual(gNumBefore, 1); 1001 assertEqual(gNumAfter, 1); 1002 1003 assertFail(tests[1]); 1004 assertEqual(gNumBefore, 2); 1005 assertEqual(gNumAfter, 2); 1006 } 1007 1008 @("issue 33") unittest { 1009 import unit_threaded.factory; 1010 import unit_threaded.testcase; 1011 1012 const testData = allTestData!"unit_threaded.tests.issue33"; 1013 assertEqual(testData.length, 1); 1014 } 1015 1016 @("issue 43") unittest { 1017 import unit_threaded.factory; 1018 import unit_threaded.asserts; 1019 import unit_threaded.tests.module_with_tests; 1020 import std.algorithm: canFind; 1021 import std.array: array; 1022 1023 const testData = allTestData!"unit_threaded.tests.module_with_tests"; 1024 assertEqual(testData.canFind!(a => a.getPath.canFind("InStruct" )), true); 1025 auto inStructTest = testData 1026 .find!(a => a.getPath.canFind("InStruct")) 1027 .array 1028 .createTestCases[0]; 1029 assertFail(inStructTest); 1030 } 1031 1032 @("@DontTest should work for unittest blocks") unittest { 1033 import unit_threaded.factory; 1034 import unit_threaded.asserts; 1035 import unit_threaded.tests.module_with_tests; 1036 import std.algorithm: canFind; 1037 import std.array: array; 1038 1039 const testData = allTestData!"unit_threaded.tests.module_with_attrs"; 1040 assertEqual(testData.canFind!(a => a.getPath.canFind("DontTestBlock" )), false); 1041 } 1042 1043 @("@ShouldFail") unittest { 1044 import unit_threaded.factory; 1045 import unit_threaded.asserts; 1046 import unit_threaded.tests.module_with_tests; 1047 import std.algorithm: find, canFind; 1048 import std.array: array; 1049 1050 const testData = allTestData!"unit_threaded.tests.module_with_attrs"; 1051 1052 auto willFail = testData 1053 .filter!(a => a.getPath.canFind("will fail")) 1054 .array 1055 .createTestCases[0]; 1056 assertPass(willFail); 1057 } 1058 1059 1060 @("@ShouldFailWith") unittest { 1061 import unit_threaded.factory; 1062 import unit_threaded.asserts; 1063 import unit_threaded.tests.module_with_attrs; 1064 import unit_threaded.should: shouldThrowExactly, UnitTestException; 1065 import std.algorithm: find, canFind; 1066 import std.array: array; 1067 1068 const testData = allTestData!"unit_threaded.tests.module_with_attrs"; 1069 1070 auto doesntFail = testData 1071 .filter!(a => a.getPath.canFind("ShouldFailWith that fails due to not failing")) 1072 .array 1073 .createTestCases[0]; 1074 assertFail(doesntFail); 1075 1076 auto wrongType = testData 1077 .find!(a => a.getPath.canFind("ShouldFailWith that fails due to wrong type")) 1078 .array 1079 .createTestCases[0]; 1080 assertFail(wrongType); 1081 1082 auto passes = testData 1083 .find!(a => a.getPath.canFind("ShouldFailWith that passes")) 1084 .array 1085 .createTestCases[0]; 1086 assertPass(passes); 1087 } 1088 1089 @("structs are not classes") unittest { 1090 import unit_threaded.should; 1091 import unit_threaded.tests.structs_are_not_classes; 1092 const testData = allTestData!"unit_threaded.tests.structs_are_not_classes"; 1093 testData.shouldBeEmpty; 1094 } 1095 1096 @("@Flaky") unittest { 1097 import unit_threaded.factory; 1098 import unit_threaded.asserts; 1099 import unit_threaded.tests.module_with_attrs; 1100 import unit_threaded.should: shouldThrowExactly, UnitTestException; 1101 import std.algorithm: find, canFind; 1102 import std.array: array; 1103 1104 const testData = allTestData!"unit_threaded.tests.module_with_attrs"; 1105 1106 auto flakyPasses = testData 1107 .filter!(a => a.getPath.canFind("flaky that passes eventually")) 1108 .array 1109 .createTestCases[0]; 1110 assertPass(flakyPasses); 1111 1112 auto flakyFails = testData 1113 .filter!(a => a.getPath.canFind("flaky that fails due to not enough retries")) 1114 .array 1115 .createTestCases[0]; 1116 assertFail(flakyFails); 1117 }