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