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