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