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