1 /** 2 Compile-time reflection to find unit tests and set their properties. 3 */ 4 module unit_threaded.reflection; 5 6 import unit_threaded.from; 7 /* 8 These standard library imports contain something important for the code below. 9 Unfortunately I don't know what they are so they're to prevent breakage. 10 */ 11 import std.traits; 12 import std.algorithm; 13 import std.array; 14 15 16 /// 17 alias void delegate() TestFunction; 18 19 /** 20 * Common data for test functions and test classes 21 */ 22 struct TestData { 23 string name; 24 TestFunction testFunction; ///only used for functions, null for classes 25 bool hidden; 26 bool shouldFail; 27 bool singleThreaded; 28 bool builtin; 29 string suffix; // append to end of getPath 30 string[] tags; 31 TypeInfo exceptionTypeInfo; // for ShouldFailWith 32 int flakyRetries = 0; 33 34 /// The test's name 35 string getPath() const pure nothrow { 36 string path = name.dup; 37 import std.array: empty; 38 if(!suffix.empty) path ~= "." ~ suffix; 39 return path; 40 } 41 42 /// If the test is a class 43 bool isTestClass() @safe const pure nothrow { 44 return testFunction is null; 45 } 46 } 47 48 49 /** 50 * Finds all test cases (functions, classes, built-in unittest blocks) 51 * Template parameters are module strings 52 */ 53 const(TestData)[] allTestData(MOD_STRINGS...)() 54 if(from!"std.meta".allSatisfy!(from!"std.traits".isSomeString, typeof(MOD_STRINGS))) 55 { 56 import std.array: join; 57 import std.range : iota; 58 import std.format : format; 59 import std.algorithm : map; 60 61 string getModulesString() { 62 string[] modules; 63 foreach(i, module_; MOD_STRINGS) modules ~= "module%d = %s".format(i, module_); 64 return modules.join(", "); 65 } 66 67 enum modulesString = getModulesString; 68 mixin("import " ~ modulesString ~ ";"); 69 mixin("return allTestData!(" ~ 70 MOD_STRINGS.length.iota.map!(i => "module%d".format(i)).join(", ") ~ 71 ");"); 72 } 73 74 75 /** 76 * Finds all test cases (functions, classes, built-in unittest blocks) 77 * Template parameters are module symbols 78 */ 79 const(TestData)[] allTestData(MOD_SYMBOLS...)() 80 if(!from!"std.meta".anySatisfy!(from!"std.traits".isSomeString, typeof(MOD_SYMBOLS))) 81 { 82 auto allTestsWithFunc(string expr)() pure { 83 import std.traits: ReturnType; 84 import std.meta: AliasSeq; 85 //tests is whatever type expr returns 86 ReturnType!(mixin(expr ~ q{!(MOD_SYMBOLS[0])})) tests; 87 foreach(module_; AliasSeq!MOD_SYMBOLS) { 88 tests ~= mixin(expr ~ q{!module_()}); //e.g. tests ~= moduleTestClasses!module_ 89 } 90 return tests; 91 } 92 93 return allTestsWithFunc!"moduleTestClasses" ~ 94 allTestsWithFunc!"moduleTestFunctions" ~ 95 allTestsWithFunc!"moduleUnitTests"; 96 } 97 98 99 private template Identity(T...) if(T.length > 0) { 100 static if(__traits(compiles, { alias x = T[0]; })) 101 alias Identity = T[0]; 102 else 103 enum Identity = T[0]; 104 } 105 106 107 /** 108 * Finds all built-in unittest blocks in the given module. 109 * Recurses into structs, classes, and unions of the module. 110 * 111 * @return An array of TestData structs 112 */ 113 TestData[] moduleUnitTests(alias module_)() pure nothrow { 114 115 // Return a name for a unittest block. If no @Name UDA is found a name is 116 // created automatically, else the UDA is used. 117 // the weird name for the first template parameter is so that it doesn't clash 118 // with a package name 119 string unittestName(alias _theUnitTest, int index)() @safe nothrow { 120 import std.conv: text, to; 121 import std.traits: fullyQualifiedName, getUDAs; 122 import std.meta: Filter; 123 import std.algorithm: startsWith, endsWith; 124 import unit_threaded.attrs: Name; 125 126 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 127 128 enum nameAttrs = getUDAs!(_theUnitTest, Name); 129 static assert(nameAttrs.length == 0 || nameAttrs.length == 1, 130 "Found multiple Name UDAs on unittest"); 131 132 enum strAttrs = Filter!(isStringUDA, __traits(getAttributes, _theUnitTest)); 133 enum hasName = nameAttrs.length || strAttrs.length == 1; 134 enum prefix = fullyQualifiedName!(__traits(parent, _theUnitTest)) ~ "."; 135 136 static if(hasName) { 137 static if(nameAttrs.length == 1) 138 return prefix ~ nameAttrs[0].value; 139 else 140 return prefix ~ strAttrs[0]; 141 } else { 142 143 // use the unittest name if available to allow for running unittests based 144 // on location 145 if(__traits(identifier, _theUnitTest).startsWith("__unittest_L")) { 146 const ret = prefix ~ __traits(identifier, _theUnitTest)[2 .. $]; 147 const suffix = "_C1"; 148 return ret.endsWith(suffix) ? ret[0 .. $ - suffix.length] : ret; 149 } 150 151 try 152 return prefix ~ "unittest" ~ index.to!string; 153 catch(Exception) 154 assert(false, text("Error converting ", index, " to string")); 155 } 156 } 157 158 void function() getUDAFunction(alias composite, alias uda)() pure nothrow { 159 import std.traits: fullyQualifiedName, moduleName, isSomeFunction, hasUDA; 160 161 // Due to: 162 // https://issues.dlang.org/show_bug.cgi?id=17441 163 // moduleName!composite might fail, so we try to import that only if 164 // if compiles, then try again with fullyQualifiedName 165 enum moduleNameStr = `import ` ~ moduleName!composite ~ `;`; 166 enum fullyQualifiedStr = `import ` ~ fullyQualifiedName!composite ~ `;`; 167 168 static if(__traits(compiles, mixin(moduleNameStr))) 169 mixin(moduleNameStr); 170 else static if(__traits(compiles, mixin(fullyQualifiedStr))) 171 mixin(fullyQualifiedStr); 172 173 void function()[] ret; 174 foreach(memberStr; __traits(allMembers, composite)) { 175 static if(__traits(compiles, Identity!(__traits(getMember, composite, memberStr)))) { 176 alias member = Identity!(__traits(getMember, composite, memberStr)); 177 static if(__traits(compiles, &member)) { 178 static if(isSomeFunction!member && hasUDA!(member, uda)) { 179 ret ~= &member; 180 } 181 } 182 } 183 } 184 185 return ret.length ? ret[0] : null; 186 } 187 188 TestData[] testData; 189 190 void addMemberUnittests(alias composite)() pure nothrow { 191 192 import unit_threaded.attrs; 193 import unit_threaded.uda: hasUtUDA; 194 import std.traits: hasUDA; 195 import std.meta: Filter, aliasSeqOf; 196 import std.algorithm: map, cartesianProduct; 197 198 foreach(index, eLtEstO; __traits(getUnitTests, composite)) { 199 200 enum dontTest = hasUDA!(eLtEstO, DontTest); 201 202 static if(!dontTest) { 203 204 enum name = unittestName!(eLtEstO, index); 205 enum hidden = hasUDA!(eLtEstO, HiddenTest); 206 enum shouldFail = hasUDA!(eLtEstO, ShouldFail) || hasUtUDA!(eLtEstO, ShouldFailWith); 207 enum singleThreaded = hasUDA!(eLtEstO, Serial); 208 enum builtin = true; 209 enum suffix = ""; 210 211 // let's check for @Values UDAs, which are actually of type ValuesImpl 212 enum isValues(alias T) = is(typeof(T)) && is(typeof(T):ValuesImpl!U, U); 213 alias valuesUDAs = Filter!(isValues, __traits(getAttributes, eLtEstO)); 214 215 enum isTags(alias T) = is(typeof(T)) && is(typeof(T) == Tags); 216 enum tags = tagsFromAttrs!(Filter!(isTags, __traits(getAttributes, eLtEstO))); 217 enum exceptionTypeInfo = getExceptionTypeInfo!eLtEstO; 218 enum flakyRetries = getFlakyRetries!(eLtEstO); 219 220 static if(valuesUDAs.length == 0) { 221 testData ~= TestData(name, 222 () { 223 auto setup = getUDAFunction!(composite, Setup); 224 auto shutdown = getUDAFunction!(composite, Shutdown); 225 226 if(setup) setup(); 227 scope(exit) if(shutdown) shutdown(); 228 229 eLtEstO(); 230 }, 231 hidden, 232 shouldFail, 233 singleThreaded, 234 builtin, 235 suffix, 236 tags, 237 exceptionTypeInfo, 238 flakyRetries); 239 } else { 240 import std.range; 241 242 // cartesianProduct doesn't work with only one range, so in the usual case 243 // of only one @Values UDA, we bind to prod with a range of tuples, just 244 // as returned by cartesianProduct. 245 246 static if(valuesUDAs.length == 1) { 247 import std.typecons; 248 enum prod = valuesUDAs[0].values.map!(a => tuple(a)); 249 } else { 250 mixin(`enum prod = cartesianProduct(` ~ valuesUDAs.length.iota.map! 251 (a => `valuesUDAs[` ~ guaranteedToString(a) ~ `].values`).join(", ") ~ `);`); 252 } 253 254 foreach(comb; aliasSeqOf!prod) { 255 enum valuesName = valuesName(comb); 256 257 static if(hasUDA!(eLtEstO, AutoTags)) 258 enum realTags = tags ~ valuesName.split(".").array; 259 else 260 enum realTags = tags; 261 262 testData ~= TestData(name ~ "." ~ valuesName, 263 () { 264 foreach(i; aliasSeqOf!(comb.length.iota)) 265 ValueHolder!(typeof(comb[i])).values[i] = comb[i]; 266 eLtEstO(); 267 }, 268 hidden, 269 shouldFail, 270 singleThreaded, 271 builtin, 272 suffix, 273 realTags, 274 exceptionTypeInfo, 275 flakyRetries); 276 } 277 } 278 } 279 } 280 } 281 282 283 // Keeps track of mangled names of everything visited. 284 bool[string] visitedMembers; 285 286 void addUnitTestsRecursively(alias composite)() pure nothrow { 287 import std.traits: fullyQualifiedName; 288 289 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 290 291 if (composite.mangleof in visitedMembers) 292 return; 293 visitedMembers[composite.mangleof] = true; 294 addMemberUnittests!composite(); 295 foreach(member; __traits(allMembers, composite)){ 296 enum notPrivate = __traits(compiles, mixin(member)); //only way I know to check if private 297 static if ( 298 notPrivate && 299 // If visibility of the member is deprecated, the next line still returns true 300 // and yet spills deprecation warning. If deprecation is turned into error, 301 // all works as intended. 302 __traits(compiles, __traits(getMember, composite, member)) && 303 __traits(compiles, __traits(allMembers, __traits(getMember, composite, member))) && 304 __traits(compiles, recurse!(__traits(getMember, composite, member))) 305 ) { 306 recurse!(__traits(getMember, composite, member)); 307 } 308 } 309 } 310 311 void recurse(child)() pure nothrow { 312 enum notPrivate = __traits(compiles, child.init); //only way I know to check if private 313 static if (is(child == class) || is(child == struct) || is(child == union)) { 314 addUnitTestsRecursively!child; 315 } 316 } 317 318 addUnitTestsRecursively!module_(); 319 return testData; 320 } 321 322 private TypeInfo getExceptionTypeInfo(alias Test)() { 323 import unit_threaded.uda: hasUtUDA, getUtUDAs; 324 import unit_threaded.attrs: ShouldFailWith; 325 326 static if(hasUtUDA!(Test, ShouldFailWith)) { 327 alias uda = getUtUDAs!(Test, ShouldFailWith)[0]; 328 return typeid(uda.Type); 329 } else 330 return null; 331 } 332 333 334 private string valuesName(T)(T tuple) { 335 import std.range: iota; 336 import std.meta: aliasSeqOf; 337 import std.array: join; 338 339 string[] parts; 340 foreach(a; aliasSeqOf!(tuple.length.iota)) 341 parts ~= guaranteedToString(tuple[a]); 342 return parts.join("."); 343 } 344 345 private string guaranteedToString(T)(T value) nothrow pure @safe { 346 import std.conv; 347 try 348 return value.to!string; 349 catch(Exception ex) 350 assert(0, "Could not convert value to string"); 351 } 352 353 private string getValueAsString(T)(T value) nothrow pure @safe { 354 import std.conv; 355 try 356 return value.to!string; 357 catch(Exception ex) 358 assert(0, "Could not convert value to string"); 359 } 360 361 362 private template isStringUDA(alias T) { 363 import std.traits: isSomeString; 364 static if(__traits(compiles, isSomeString!(typeof(T)))) 365 enum isStringUDA = isSomeString!(typeof(T)); 366 else 367 enum isStringUDA = false; 368 } 369 370 @safe pure unittest { 371 static assert(isStringUDA!"foo"); 372 static assert(!isStringUDA!5); 373 } 374 375 private template isPrivate(alias module_, string moduleMember) { 376 import unit_threaded.uda: HasTypes; 377 378 alias ut_mmbr__ = Identity!(__traits(getMember, module_, moduleMember)); 379 380 static if(__traits(compiles, isSomeFunction!(ut_mmbr__))) { 381 static if(__traits(compiles, &ut_mmbr__)) 382 enum isPrivate = false; 383 else static if(__traits(compiles, new ut_mmbr__)) 384 enum isPrivate = false; 385 else static if(__traits(compiles, HasTypes!ut_mmbr__)) 386 enum isPrivate = !HasTypes!ut_mmbr__; 387 else 388 enum isPrivate = true; 389 } else { 390 enum isPrivate = true; 391 } 392 } 393 394 395 // if this member is a test function or class, given the predicate 396 private template PassesTestPred(alias module_, alias pred, string moduleMember) { 397 import std.traits: fullyQualifiedName; 398 import unit_threaded.meta: importMember; 399 import unit_threaded.uda: HasAttribute; 400 import unit_threaded.attrs: DontTest; 401 402 //should be the line below instead but a compiler bug prevents it 403 //mixin(importMember!module_(moduleMember)); 404 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); 405 alias I(T...) = T; 406 static if(!__traits(compiles, I!(__traits(getMember, module_, moduleMember)))) { 407 enum PassesTestPred = false; 408 } else { 409 alias member = I!(__traits(getMember, module_, moduleMember)); 410 411 template canCheckIfSomeFunction(T...) { 412 enum canCheckIfSomeFunction = T.length == 1 && __traits(compiles, isSomeFunction!(T[0])); 413 } 414 415 private string funcCallMixin(alias T)() { 416 import std.conv: to; 417 string[] args; 418 foreach(i, ParamType; Parameters!T) { 419 args ~= `arg` ~ i.to!string; 420 } 421 422 return moduleMember ~ `(` ~ args.join(`,`) ~ `);`; 423 } 424 425 private string argsMixin(alias T)() { 426 import std.conv: to; 427 string[] args; 428 foreach(i, ParamType; Parameters!T) { 429 args ~= ParamType.stringof ~ ` arg` ~ i.to!string ~ `;`; 430 } 431 432 return args.join("\n"); 433 } 434 435 template canCallMember() { 436 void _f() { 437 mixin(argsMixin!member); 438 mixin(funcCallMixin!member); 439 } 440 } 441 442 template canInstantiate() { 443 void _f() { 444 mixin(`auto _ = new ` ~ moduleMember ~ `;`); 445 } 446 } 447 448 template isPrivate() { 449 static if(!canCheckIfSomeFunction!member) { 450 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember)); 451 } else { 452 static if(isSomeFunction!member) { 453 enum isPrivate = !__traits(compiles, canCallMember!()); 454 } else static if(is(member)) { 455 static if(isAggregateType!member) 456 enum isPrivate = !__traits(compiles, canInstantiate!()); 457 else 458 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember)); 459 } else { 460 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember)); 461 } 462 } 463 } 464 465 enum notPrivate = !isPrivate!(); 466 enum PassesTestPred = !isPrivate!() && pred!(module_, moduleMember) && 467 !HasAttribute!(module_, moduleMember, DontTest); 468 } 469 } 470 471 472 /** 473 * Finds all test classes (classes implementing a test() function) 474 * in the given module 475 */ 476 TestData[] moduleTestClasses(alias module_)() pure nothrow { 477 478 template isTestClass(alias module_, string moduleMember) { 479 import unit_threaded.meta: importMember; 480 import unit_threaded.uda: HasAttribute; 481 import unit_threaded.attrs: UnitTest; 482 import std.traits: isAggregateType; 483 484 alias member = Identity!(__traits(getMember, module_, moduleMember)); 485 486 static if(.isPrivate!(module_, moduleMember)) { 487 enum isTestClass = false; 488 } else static if(!__traits(compiles, isAggregateType!(member))) { 489 enum isTestClass = false; 490 } else static if(!isAggregateType!(member)) { 491 enum isTestClass = false; 492 } else static if(!__traits(compiles, { return new member; })) { 493 enum isTestClass = false; //can't new it, can't use it 494 } else { 495 enum hasUnitTest = HasAttribute!(module_, moduleMember, UnitTest); 496 enum hasTestMethod = __traits(hasMember, member, "test"); 497 498 enum isTestClass = is(member == class) && (hasTestMethod || hasUnitTest); 499 } 500 } 501 502 return moduleTestData!(module_, isTestClass, memberTestData); 503 } 504 505 506 /** 507 * Finds all test functions in the given module. 508 * Returns an array of TestData structs 509 */ 510 TestData[] moduleTestFunctions(alias module_)() pure { 511 512 import unit_threaded.uda: isTypesAttr; 513 514 template isTestFunction(alias module_, string moduleMember) { 515 import unit_threaded.meta: importMember; 516 import unit_threaded.attrs: UnitTest; 517 import unit_threaded.uda: HasAttribute, GetTypes; 518 import std.meta: AliasSeq; 519 import std.traits: isSomeFunction; 520 521 alias member = Identity!(__traits(getMember, module_, moduleMember)); 522 523 static if(.isPrivate!(module_, moduleMember)) { 524 enum isTestFunction = false; 525 } else static if(AliasSeq!(member).length != 1) { 526 enum isTestFunction = false; 527 } else static if(isSomeFunction!member) { 528 enum isTestFunction = hasTestPrefix!(module_, moduleMember) || 529 HasAttribute!(module_, moduleMember, UnitTest); 530 } else static if(__traits(compiles, __traits(getAttributes, member))) { 531 // in this case we handle the possibility of a template function with 532 // the @Types UDA attached to it 533 alias types = GetTypes!member; 534 enum isTestFunction = hasTestPrefix!(module_, moduleMember) && 535 types.length > 0; 536 } else { 537 enum isTestFunction = false; 538 } 539 540 } 541 542 template hasTestPrefix(alias module_, string memberName) { 543 import std.uni: isUpper; 544 import unit_threaded.meta: importMember; 545 546 alias member = Identity!(__traits(getMember, module_, memberName)); 547 548 enum prefix = "test"; 549 enum minSize = prefix.length + 1; 550 551 static if(memberName.length >= minSize && 552 memberName[0 .. prefix.length] == prefix && 553 isUpper(memberName[prefix.length])) { 554 enum hasTestPrefix = true; 555 } else { 556 enum hasTestPrefix = false; 557 } 558 } 559 560 return moduleTestData!(module_, isTestFunction, createFuncTestData); 561 } 562 563 private TestData[] createFuncTestData(alias module_, string moduleMember)() { 564 import unit_threaded.meta: importMember; 565 import unit_threaded.uda: GetAttributes, HasAttribute, GetTypes, HasTypes; 566 import unit_threaded.attrs; 567 import std.meta: aliasSeqOf; 568 569 mixin(importMember!module_(moduleMember)); 570 /* 571 Get all the test functions for this module member. There might be more than one 572 when using parametrized unit tests. 573 574 Examples: 575 ------ 576 void testFoo() {} // -> the array contains one element, testFoo 577 @(1, 2, 3) void testBar(int) {} // The array contains 3 elements, one for each UDA value 578 @Types!(int, float) void testBaz(T)() {} //The array contains 2 elements, one for each type 579 ------ 580 */ 581 // if the predicate returned true (which is always the case here), then it's either 582 // a regular function or a templated one. If regular we can get a pointer to it 583 enum isRegularFunction = __traits(compiles, &__traits(getMember, module_, moduleMember)); 584 585 static if(isRegularFunction) { 586 587 enum func = &__traits(getMember, module_, moduleMember); 588 enum arity = arity!func; 589 590 static if(arity == 0) 591 // the reason we're creating a lambda to call the function is that test functions 592 // are ordinary functions, but we're storing delegates 593 return [ memberTestData!(module_, moduleMember)(() { func(); }) ]; //simple case, just call the function 594 else { 595 596 // the function has parameters, check if it has UDAs for value parameters to be passed to it 597 alias params = Parameters!func; 598 599 import std.range: iota; 600 import std.algorithm: any; 601 import std.typecons: tuple, Tuple; 602 603 bool hasAttributesForAllParams() { 604 auto ret = true; 605 foreach(p; params) { 606 if(tuple(GetAttributes!(module_, moduleMember, p)).length == 0) { 607 ret = false; 608 } 609 } 610 return ret; 611 } 612 613 static if(!hasAttributesForAllParams) { 614 import std.conv: text; 615 pragma(msg, text("Warning: ", moduleMember, " passes the criteria for a value-parameterized test function", 616 " but doesn't have the appropriate value UDAs.\n", 617 " Consider changing its name or annotating it with @DontTest")); 618 return []; 619 } else { 620 621 static if(arity == 1) { 622 // bind a range of tuples to prod just as cartesianProduct returns 623 enum prod = [GetAttributes!(module_, moduleMember, params[0])].map!(a => tuple(a)); 624 } else { 625 import std.conv: text; 626 627 mixin(`enum prod = cartesianProduct(` ~ params.length.iota.map! 628 (a => `[GetAttributes!(module_, moduleMember, params[` ~ guaranteedToString(a) ~ `])]`).join(", ") ~ `);`); 629 } 630 631 TestData[] testData; 632 foreach(comb; aliasSeqOf!prod) { 633 enum valuesName = valuesName(comb); 634 635 static if(HasAttribute!(module_, moduleMember, AutoTags)) 636 enum extraTags = valuesName.split(".").array; 637 else 638 enum string[] extraTags = []; 639 640 641 testData ~= memberTestData!(module_, moduleMember, extraTags)( 642 // func(value0, value1, ...) 643 () { func(comb.expand); }, 644 valuesName); 645 } 646 647 return testData; 648 } 649 } 650 } else static if(HasTypes!(mixin(moduleMember))) { //template function with @Types 651 alias types = GetTypes!(mixin(moduleMember)); 652 TestData[] testData; 653 foreach(type; types) { 654 655 static if(HasAttribute!(module_, moduleMember, AutoTags)) 656 enum extraTags = [type.stringof]; 657 else 658 enum string[] extraTags = []; 659 660 alias member = Identity!(mixin(moduleMember)); 661 662 testData ~= memberTestData!(module_, moduleMember, extraTags)( 663 () { member!type(); }, 664 type.stringof); 665 } 666 return testData; 667 } else { 668 return []; 669 } 670 } 671 672 673 674 // this funtion returns TestData for either classes or test functions 675 // built-in unittest modules are handled by moduleUnitTests 676 // pred determines what qualifies as a test 677 // createTestData must return TestData[] 678 private TestData[] moduleTestData(alias module_, alias pred, alias createTestData)() pure { 679 import std.traits: fullyQualifiedName; 680 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 681 682 TestData[] testData; 683 684 foreach(moduleMember; __traits(allMembers, module_)) { 685 686 static if(PassesTestPred!(module_, pred, moduleMember)) 687 testData ~= createTestData!(module_, moduleMember); 688 } 689 690 return testData; 691 692 } 693 694 // TestData for a member of a module (either a test function or test class) 695 private TestData memberTestData(alias module_, string moduleMember, string[] extraTags = []) 696 (TestFunction testFunction = null, string suffix = "") { 697 698 import unit_threaded.uda: HasAttribute, GetAttributes, hasUtUDA; 699 import unit_threaded.attrs; 700 import std.traits: fullyQualifiedName; 701 702 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible 703 704 immutable singleThreaded = HasAttribute!(module_, moduleMember, Serial); 705 enum builtin = false; 706 enum tags = tagsFromAttrs!(GetAttributes!(module_, moduleMember, Tags)); 707 enum exceptionTypeInfo = getExceptionTypeInfo!(mixin(moduleMember)); 708 enum shouldFail = 709 HasAttribute!(module_, moduleMember, ShouldFail) || 710 hasUtUDA!(mixin(moduleMember), ShouldFailWith); 711 enum flakyRetries = getFlakyRetries!(mixin(moduleMember)); 712 713 return TestData(fullyQualifiedName!module_~ "." ~ moduleMember, 714 testFunction, 715 HasAttribute!(module_, moduleMember, HiddenTest), 716 shouldFail, 717 singleThreaded, 718 builtin, 719 suffix, 720 tags ~ extraTags, 721 exceptionTypeInfo, 722 flakyRetries); 723 } 724 725 private int getFlakyRetries(alias test)() { 726 import unit_threaded.attrs: Flaky; 727 import std.traits: getUDAs; 728 import std.conv: text; 729 730 alias flakies = getUDAs!(test, Flaky); 731 732 static assert(flakies.length == 0 || flakies.length == 1, 733 text("Only 1 @Flaky allowed, found ", flakies.length, " on ", 734 __traits(identifier, test))); 735 736 static if(flakies.length == 1) { 737 static if(is(flakies[0])) 738 return Flaky.defaultRetries; 739 else 740 return flakies[0].retries; 741 } else 742 return 0; 743 } 744 745 string[] tagsFromAttrs(T...)() { 746 static assert(T.length <= 1, "@Tags can only be applied once"); 747 static if(T.length) 748 return T[0].values; 749 else 750 return []; 751 }