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