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