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