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