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