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