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 enum isTestClass = false; 454 } else static if(!isAggregateType!(member)) { 455 enum isTestClass = false; 456 } else static if(!__traits(compiles, { return new member; })) { 457 enum isTestClass = false; //can't new it, can't use it 458 } else { 459 enum hasUnitTest = HasAttribute!(module_, moduleMember, UnitTest); 460 enum hasTestMethod = __traits(hasMember, member, "test"); 461 462 enum isTestClass = is(member == class) && (hasTestMethod || hasUnitTest); 463 } 464 } 465 466 467 return moduleTestData!(module_, isTestClass, memberTestData); 468 } 469 470 471 /** 472 * Finds all test functions in the given module. 473 * Returns an array of TestData structs 474 */ 475 TestData[] moduleTestFunctions(alias module_)() pure { 476 477 enum isTypesAttr(alias T) = is(T) && is(T:Types!U, U...); 478 479 template isTestFunction(alias module_, string moduleMember) { 480 import unit_threaded.meta: importMember; 481 import unit_threaded.attrs: UnitTest; 482 import unit_threaded.uda: HasAttribute, GetTypes; 483 import std.meta: AliasSeq; 484 import std.traits: isSomeFunction; 485 486 mixin(importMember!module_(moduleMember)); 487 488 static if(.isPrivate!(module_, moduleMember)) { 489 enum isTestFunction = false; 490 } else static if(AliasSeq!(mixin(moduleMember)).length != 1) { 491 enum isTestFunction = false; 492 } else static if(isSomeFunction!(mixin(moduleMember))) { 493 enum isTestFunction = hasTestPrefix!(module_, moduleMember) || 494 HasAttribute!(module_, moduleMember, UnitTest); 495 } else static if(__traits(compiles, __traits(getAttributes, mixin(moduleMember)))) { 496 // in this case we handle the possibility of a template function with 497 // the @Types UDA attached to it 498 alias types = GetTypes!(mixin(moduleMember)); 499 enum isTestFunction = hasTestPrefix!(module_, moduleMember) && 500 types.length > 0; 501 } else { 502 enum isTestFunction = false; 503 } 504 505 } 506 507 template hasTestPrefix(alias module_, string member) { 508 import std.uni: isUpper; 509 import unit_threaded.meta: importMember; 510 511 mixin(importMember!module_(member)); 512 513 enum prefix = "test"; 514 enum minSize = prefix.length + 1; 515 516 static if(member.length >= minSize && member[0 .. prefix.length] == prefix && 517 isUpper(member[prefix.length])) { 518 enum hasTestPrefix = true; 519 } else { 520 enum hasTestPrefix = false; 521 } 522 } 523 524 return moduleTestData!(module_, isTestFunction, createFuncTestData); 525 } 526 527 private TestData[] createFuncTestData(alias module_, string moduleMember)() { 528 import unit_threaded.meta: importMember; 529 import unit_threaded.uda: GetAttributes, HasAttribute, GetTypes, HasTypes; 530 import unit_threaded.attrs; 531 import std.meta: aliasSeqOf; 532 533 mixin(importMember!module_(moduleMember)); 534 /* 535 Get all the test functions for this module member. There might be more than one 536 when using parametrized unit tests. 537 538 Examples: 539 ------ 540 void testFoo() {} // -> the array contains one element, testFoo 541 @(1, 2, 3) void testBar(int) {} // The array contains 3 elements, one for each UDA value 542 @Types!(int, float) void testBaz(T)() {} //The array contains 2 elements, one for each type 543 ------ 544 */ 545 // if the predicate returned true (which is always the case here), then it's either 546 // a regular function or a templated one. If regular we can get a pointer to it 547 enum isRegularFunction = __traits(compiles, &__traits(getMember, module_, moduleMember)); 548 549 static if(isRegularFunction) { 550 551 enum func = &__traits(getMember, module_, moduleMember); 552 enum arity = arity!func; 553 554 static if(arity == 0) 555 // the reason we're creating a lambda to call the function is that test functions 556 // are ordinary functions, but we're storing delegates 557 return [ memberTestData!(module_, moduleMember)(() { func(); }) ]; //simple case, just call the function 558 else { 559 560 // the function has parameters, check if it has UDAs for value parameters to be passed to it 561 alias params = Parameters!func; 562 563 import std.range: iota; 564 import std.algorithm: any; 565 import std.typecons: tuple, Tuple; 566 567 bool hasAttributesForAllParams() { 568 auto ret = true; 569 foreach(p; params) { 570 if(tuple(GetAttributes!(module_, moduleMember, p)).length == 0) { 571 ret = false; 572 } 573 } 574 return ret; 575 } 576 577 static if(!hasAttributesForAllParams) { 578 import std.conv: text; 579 pragma(msg, text("Warning: ", moduleMember, " passes the criteria for a value-parameterized test function", 580 " but doesn't have the appropriate value UDAs.\n", 581 " Consider changing its name or annotating it with @DontTest")); 582 return []; 583 } else { 584 585 static if(arity == 1) { 586 // bind a range of tuples to prod just as cartesianProduct returns 587 enum prod = [GetAttributes!(module_, moduleMember, params[0])].map!(a => tuple(a)); 588 } else { 589 import std.conv: text; 590 591 mixin(`enum prod = cartesianProduct(` ~ params.length.iota.map! 592 (a => `[GetAttributes!(module_, moduleMember, params[` ~ guaranteedToString(a) ~ `])]`).join(", ") ~ `);`); 593 } 594 595 TestData[] testData; 596 foreach(comb; aliasSeqOf!prod) { 597 enum valuesName = valuesName(comb); 598 599 static if(HasAttribute!(module_, moduleMember, AutoTags)) 600 enum extraTags = valuesName.split(".").array; 601 else 602 enum string[] extraTags = []; 603 604 605 testData ~= memberTestData!(module_, moduleMember, extraTags)( 606 // func(value0, value1, ...) 607 () { func(comb.expand); }, 608 valuesName); 609 } 610 611 return testData; 612 } 613 } 614 } else static if(HasTypes!(mixin(moduleMember))) { //template function with @Types 615 alias types = GetTypes!(mixin(moduleMember)); 616 TestData[] testData; 617 foreach(type; types) { 618 619 static if(HasAttribute!(module_, moduleMember, AutoTags)) 620 enum extraTags = [type.stringof]; 621 else 622 enum string[] extraTags = []; 623 624 alias member = Identity!(mixin(moduleMember)); 625 626 testData ~= memberTestData!(module_, moduleMember, extraTags)( 627 () { member!type(); }, 628 type.stringof); 629 } 630 return testData; 631 } else { 632 return []; 633 } 634 } 635 636 637 638 // this funtion returns TestData for either classes or test functions 639 // built-in unittest modules are handled by moduleUnitTests 640 // pred determines what qualifies as a test 641 // createTestData must return TestData[] 642 private TestData[] moduleTestData(alias module_, alias pred, alias createTestData)() pure { 643 import std.traits: fullyQualifiedName; 644 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 645 TestData[] testData; 646 foreach(moduleMember; __traits(allMembers, module_)) { 647 648 static if(PassesTestPred!(module_, pred, moduleMember)) 649 testData ~= createTestData!(module_, moduleMember); 650 } 651 652 return testData; 653 654 } 655 656 // TestData for a member of a module (either a test function or test class) 657 private TestData memberTestData(alias module_, string moduleMember, string[] extraTags = []) 658 (TestFunction testFunction = null, string suffix = "") { 659 660 import unit_threaded.uda: HasAttribute, GetAttributes, hasUtUDA; 661 import unit_threaded.attrs; 662 import std.traits: fullyQualifiedName; 663 664 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 665 666 immutable singleThreaded = HasAttribute!(module_, moduleMember, Serial); 667 enum builtin = false; 668 enum tags = tagsFromAttrs!(GetAttributes!(module_, moduleMember, Tags)); 669 enum exceptionTypeInfo = getExceptionTypeInfo!(mixin(moduleMember)); 670 671 return TestData(fullyQualifiedName!module_~ "." ~ moduleMember, 672 testFunction, 673 HasAttribute!(module_, moduleMember, HiddenTest), 674 HasAttribute!(module_, moduleMember, ShouldFail) || hasUtUDA!(mixin(moduleMember), ShouldFailWith), 675 singleThreaded, 676 builtin, 677 suffix, 678 tags ~ extraTags, 679 exceptionTypeInfo); 680 } 681 682 string[] tagsFromAttrs(T...)() { 683 static assert(T.length <= 1, "@Tags can only be applied once"); 684 static if(T.length) 685 return T[0].values; 686 else 687 return []; 688 } 689 690 version(unittest) { 691 692 import unit_threaded.tests.module_with_tests; //defines tests and non-tests 693 import unit_threaded.asserts; 694 import std.algorithm; 695 import std.array; 696 697 //helper function for the unittest blocks below 698 private auto addModPrefix(string[] elements, 699 string module_ = "unit_threaded.tests.module_with_tests") nothrow { 700 return elements.map!(a => module_ ~ "." ~ a).array; 701 } 702 } 703 704 unittest { 705 const expected = addModPrefix([ "FooTest", "BarTest", "Blergh", "Issue83"]); 706 const actual = moduleTestClasses!(unit_threaded.tests.module_with_tests). 707 map!(a => a.name).array; 708 assertEqual(actual, expected); 709 } 710 711 unittest { 712 const expected = addModPrefix([ "testFoo", "testBar", "funcThatShouldShowUpCosOfAttr"]); 713 const actual = moduleTestFunctions!(unit_threaded.tests.module_with_tests). 714 map!(a => a.getPath).array; 715 assertEqual(actual, expected); 716 } 717 718 719 unittest { 720 const expected = addModPrefix(["unittest0", "unittest1", "myUnitTest", 721 "StructWithUnitTests.InStruct", "StructWithUnitTests.unittest1"]); 722 const actual = moduleUnitTests!(unit_threaded.tests.module_with_tests). 723 map!(a => a.name).array; 724 assertEqual(actual, expected); 725 } 726 727 version(unittest) { 728 import unit_threaded.testcase: TestCase; 729 private void assertFail(TestCase test, string file = __FILE__, size_t line = __LINE__) { 730 import core.exception; 731 import std.conv; 732 733 test.silence; 734 assert(test() != [], 735 file ~ ":" ~ line.to!string ~ " Expected test case " ~ test.getPath ~ 736 " to fail but it didn't"); 737 } 738 739 private void assertPass(TestCase test, string file = __FILE__, size_t line = __LINE__) { 740 import unit_threaded.should: fail; 741 if(test() != []) 742 fail("'" ~ test.getPath ~ "' was expected to pass but failed", file, line); 743 } 744 } 745 746 @("Test that parametrized value tests work") 747 unittest { 748 import unit_threaded.factory; 749 import unit_threaded.testcase; 750 import unit_threaded.tests.parametrized; 751 752 const testData = allTestData!(unit_threaded.tests.parametrized). 753 filter!(a => a.name.endsWith("testValues")).array; 754 755 auto tests = createTestCases(testData); 756 assertEqual(tests.length, 3); 757 758 // the first and third test should pass, the second should fail 759 assertPass(tests[0]); 760 assertPass(tests[2]); 761 762 assertFail(tests[1]); 763 } 764 765 766 @("Test that parametrized type tests work") 767 unittest { 768 import unit_threaded.factory; 769 import unit_threaded.testcase; 770 import unit_threaded.tests.parametrized; 771 772 const testData = allTestData!(unit_threaded.tests.parametrized). 773 filter!(a => a.name.endsWith("testTypes")).array; 774 const expected = addModPrefix(["testTypes.float", "testTypes.int"], 775 "unit_threaded.tests.parametrized"); 776 const actual = testData.map!(a => a.getPath).array; 777 assertEqual(actual, expected); 778 779 auto tests = createTestCases(testData); 780 assertEqual(tests.map!(a => a.getPath).array, expected); 781 782 assertPass(tests[1]); 783 assertFail(tests[0]); 784 } 785 786 @("Value parametrized built-in unittests") 787 unittest { 788 import unit_threaded.factory; 789 import unit_threaded.testcase; 790 import unit_threaded.tests.parametrized; 791 792 const testData = allTestData!(unit_threaded.tests.parametrized). 793 filter!(a => a.name.canFind("builtinIntValues")).array; 794 795 auto tests = createTestCases(testData); 796 assertEqual(tests.length, 4); 797 798 // these should be ok 799 assertPass(tests[1]); 800 801 //these should fail 802 assertFail(tests[0]); 803 assertFail(tests[2]); 804 assertFail(tests[3]); 805 } 806 807 808 @("Tests can be selected by tags") unittest { 809 import unit_threaded.factory; 810 import unit_threaded.testcase; 811 import unit_threaded.tests.tags; 812 813 const testData = allTestData!(unit_threaded.tests.tags).array; 814 auto testsNoTags = createTestCases(testData); 815 assertEqual(testsNoTags.length, 4); 816 assertPass(testsNoTags[0]); 817 assertFail(testsNoTags.find!(a => a.getPath.canFind("unittest1")).front); 818 assertFail(testsNoTags[2]); 819 assertFail(testsNoTags[3]); 820 821 auto testsNinja = createTestCases(testData, ["@ninja"]); 822 assertEqual(testsNinja.length, 1); 823 assertPass(testsNinja[0]); 824 825 auto testsMake = createTestCases(testData, ["@make"]); 826 assertEqual(testsMake.length, 3); 827 assertPass(testsMake.find!(a => a.getPath.canFind("testMake")).front); 828 assertPass(testsMake.find!(a => a.getPath.canFind("unittest0")).front); 829 assertFail(testsMake.find!(a => a.getPath.canFind("unittest2")).front); 830 831 auto testsNotNinja = createTestCases(testData, ["~@ninja"]); 832 assertEqual(testsNotNinja.length, 3); 833 assertPass(testsNotNinja.find!(a => a.getPath.canFind("testMake")).front); 834 assertFail(testsNotNinja.find!(a => a.getPath.canFind("unittest1")).front); 835 assertFail(testsNotNinja.find!(a => a.getPath.canFind("unittest2")).front); 836 837 assertEqual(createTestCases(testData, ["unit_threaded.tests.tags.testMake", "@ninja"]).length, 0); 838 } 839 840 @("Parametrized built-in tests with @AutoTags get tagged by value") 841 unittest { 842 import unit_threaded.factory; 843 import unit_threaded.testcase; 844 import unit_threaded.tests.parametrized; 845 846 const testData = allTestData!(unit_threaded.tests.parametrized). 847 filter!(a => a.name.canFind("builtinIntValues")).array; 848 849 auto two = createTestCases(testData, ["@2"]); 850 851 assertEqual(two.length, 1); 852 assertFail(two[0]); 853 854 auto three = createTestCases(testData, ["@3"]); 855 assertEqual(three.length, 1); 856 assertPass(three[0]); 857 } 858 859 @("Value parametrized function tests with @AutoTags get tagged by value") 860 unittest { 861 import unit_threaded.factory; 862 import unit_threaded.testcase; 863 import unit_threaded.tests.parametrized; 864 865 const testData = allTestData!(unit_threaded.tests.parametrized). 866 filter!(a => a.name.canFind("testValues")).array; 867 868 auto two = createTestCases(testData, ["@2"]); 869 assertEqual(two.length, 1); 870 assertFail(two[0]); 871 } 872 873 @("Type parameterized tests with @AutoTags get tagged by type") 874 unittest { 875 import unit_threaded.factory; 876 import unit_threaded.testcase; 877 import unit_threaded.tests.parametrized; 878 879 const testData = allTestData!(unit_threaded.tests.parametrized). 880 filter!(a => a.name.canFind("testTypes")).array; 881 882 auto tests = createTestCases(testData, ["@int"]); 883 assertEqual(tests.length, 1); 884 assertPass(tests[0]); 885 } 886 887 @("Cartesian parameterized built-in values") unittest { 888 import unit_threaded.factory; 889 import unit_threaded.testcase; 890 import unit_threaded.should: shouldBeSameSetAs; 891 import unit_threaded.tests.parametrized; 892 import unit_threaded.attrs: getValue; 893 894 const testData = allTestData!(unit_threaded.tests.parametrized). 895 filter!(a => a.name.canFind("cartesianBuiltinNoAutoTags")).array; 896 897 auto tests = createTestCases(testData); 898 tests.map!(a => a.getPath).array.shouldBeSameSetAs( 899 addModPrefix(["foo.red", "foo.blue", "foo.green", "bar.red", "bar.blue", "bar.green"]. 900 map!(a => "cartesianBuiltinNoAutoTags." ~ a).array, 901 "unit_threaded.tests.parametrized")); 902 assertEqual(tests.length, 6); 903 904 auto fooRed = tests.find!(a => a.getPath.canFind("foo.red")).front; 905 assertPass(fooRed); 906 assertEqual(getValue!(string, 0), "foo"); 907 assertEqual(getValue!(string, 1), "red"); 908 assertEqual(testData.find!(a => a.getPath.canFind("foo.red")).front.tags, []); 909 910 auto barGreen = tests.find!(a => a.getPath.canFind("bar.green")).front; 911 assertFail(barGreen); 912 assertEqual(getValue!(string, 0), "bar"); 913 assertEqual(getValue!(string, 1), "green"); 914 915 assertEqual(testData.find!(a => a.getPath.canFind("bar.green")).front.tags, []); 916 assertEqual(allTestData!(unit_threaded.tests.parametrized). 917 filter!(a => a.name.canFind("cartesianBuiltinAutoTags")).array. 918 find!(a => a.getPath.canFind("bar.green")).front.tags, 919 ["bar", "green"]); 920 } 921 922 @("Cartesian parameterized function values") unittest { 923 import unit_threaded.factory; 924 import unit_threaded.testcase; 925 import unit_threaded.should: shouldBeSameSetAs; 926 927 const testData = allTestData!(unit_threaded.tests.parametrized). 928 filter!(a => a.name.canFind("CartesianFunction")).array; 929 930 auto tests = createTestCases(testData); 931 tests.map!(a => a.getPath).array.shouldBeSameSetAs( 932 addModPrefix(["1.foo", "1.bar", "2.foo", "2.bar", "3.foo", "3.bar"]. 933 map!(a => "testCartesianFunction." ~ a).array, 934 "unit_threaded.tests.parametrized")); 935 936 foreach(test; tests) { 937 test.getPath.canFind("2.bar") 938 ? assertPass(test) 939 : assertFail(test); 940 } 941 942 assertEqual(testData.find!(a => a.getPath.canFind("2.bar")).front.tags, 943 ["2", "bar"]); 944 945 } 946 947 @("module setup and shutdown") 948 unittest { 949 import unit_threaded.testcase; 950 import unit_threaded.factory; 951 import unit_threaded.tests.module_with_setup: gNumBefore, gNumAfter; 952 953 const testData = allTestData!"unit_threaded.tests.module_with_setup".array; 954 auto tests = createTestCases(testData); 955 assertEqual(tests.length, 2); 956 957 assertPass(tests[0]); 958 assertEqual(gNumBefore, 1); 959 assertEqual(gNumAfter, 1); 960 961 assertFail(tests[1]); 962 assertEqual(gNumBefore, 2); 963 assertEqual(gNumAfter, 2); 964 } 965 966 @("issue 33") unittest { 967 import unit_threaded.factory; 968 import unit_threaded.testcase; 969 970 const testData = allTestData!"unit_threaded.tests.issue33"; 971 assertEqual(testData.length, 1); 972 } 973 974 @("issue 43") unittest { 975 import unit_threaded.factory; 976 import unit_threaded.asserts; 977 import unit_threaded.tests.module_with_tests; 978 import std.algorithm: canFind; 979 import std.array: array; 980 981 const testData = allTestData!"unit_threaded.tests.module_with_tests"; 982 assertEqual(testData.canFind!(a => a.getPath.canFind("InStruct" )), true); 983 auto inStructTest = testData 984 .find!(a => a.getPath.canFind("InStruct")) 985 .array 986 .createTestCases[0]; 987 assertFail(inStructTest); 988 } 989 990 @("@DontTest should work for unittest blocks") unittest { 991 import unit_threaded.factory; 992 import unit_threaded.asserts; 993 import unit_threaded.tests.module_with_tests; 994 import std.algorithm: canFind; 995 import std.array: array; 996 997 const testData = allTestData!"unit_threaded.tests.module_with_attrs"; 998 assertEqual(testData.canFind!(a => a.getPath.canFind("DontTestBlock" )), false); 999 } 1000 1001 @("@ShouldFail") unittest { 1002 import unit_threaded.factory; 1003 import unit_threaded.asserts; 1004 import unit_threaded.tests.module_with_tests; 1005 import std.algorithm: find, canFind; 1006 import std.array: array; 1007 1008 const testData = allTestData!"unit_threaded.tests.module_with_attrs"; 1009 1010 auto willFail = testData 1011 .filter!(a => a.getPath.canFind("will fail")) 1012 .array 1013 .createTestCases[0]; 1014 assertPass(willFail); 1015 } 1016 1017 1018 @("@ShouldFailWith") unittest { 1019 import unit_threaded.factory; 1020 import unit_threaded.asserts; 1021 import unit_threaded.tests.module_with_attrs; 1022 import unit_threaded.should: shouldThrowExactly, UnitTestException; 1023 import std.algorithm: find, canFind; 1024 import std.array: array; 1025 1026 const testData = allTestData!"unit_threaded.tests.module_with_attrs"; 1027 1028 auto doesntFail = testData 1029 .filter!(a => a.getPath.canFind("ShouldFailWith that fails due to not failing")) 1030 .array 1031 .createTestCases[0]; 1032 assertFail(doesntFail); 1033 1034 auto wrongType = testData 1035 .find!(a => a.getPath.canFind("ShouldFailWith that fails due to wrong type")) 1036 .array 1037 .createTestCases[0]; 1038 assertFail(wrongType); 1039 1040 auto passes = testData 1041 .find!(a => a.getPath.canFind("ShouldFailWith that passes")) 1042 .array 1043 .createTestCases[0]; 1044 assertPass(passes); 1045 } 1046 1047 @("structs are not classes") unittest { 1048 import unit_threaded.should; 1049 import unit_threaded.tests.structs_are_not_classes; 1050 const testData = allTestData!"unit_threaded.tests.structs_are_not_classes"; 1051 testData.shouldBeEmpty; 1052 }