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