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