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         static if(isPrivate!(module_, moduleMember)) {
408             enum isTestClass = false;
409         } else static if(!__traits(compiles, isAggregateType!(mixin(moduleMember)))) {
410             enum isTestClass = false;
411         } else static if(!isAggregateType!(mixin(moduleMember))) {
412             enum isTestClass = false;
413         } else static if(!__traits(compiles, mixin("new " ~ moduleMember))) {
414             enum isTestClass = false; //can't new it, can't use it
415         } else {
416             enum hasUnitTest = HasAttribute!(module_, moduleMember, UnitTest);
417             enum hasTestMethod = __traits(hasMember, mixin(moduleMember), "test");
418             enum isTestClass = hasTestMethod || hasUnitTest;
419         }
420     }
421 
422 
423     return moduleTestData!(module_, isTestClass, memberTestData);
424 }
425 
426 
427 /**
428  * Finds all test functions in the given module.
429  * Returns an array of TestData structs
430  */
431 TestData[] moduleTestFunctions(alias module_)() pure {
432 
433     enum isTypesAttr(alias T) = is(T) && is(T:Types!U, U...);
434 
435     template isTestFunction(alias module_, string moduleMember) {
436         mixin(importMember!module_(moduleMember));
437 
438         static if(isPrivate!(module_, moduleMember)) {
439             enum isTestFunction = false;
440         } else static if(AliasSeq!(mixin(moduleMember)).length != 1) {
441             enum isTestFunction = false;
442         } else static if(isSomeFunction!(mixin(moduleMember))) {
443             enum isTestFunction = hasTestPrefix!(module_, moduleMember) ||
444                                   HasAttribute!(module_, moduleMember, UnitTest);
445         } else static if(__traits(compiles, __traits(getAttributes, mixin(moduleMember)))) {
446             // in this case we handle the possibility of a template function with
447             // the @Types UDA attached to it
448             alias types = GetTypes!(mixin(moduleMember));
449             enum isTestFunction = hasTestPrefix!(module_, moduleMember) &&
450                                   types.length > 0;
451         } else {
452             enum isTestFunction = false;
453         }
454 
455     }
456 
457     template hasTestPrefix(alias module_, string member) {
458         import std.uni: isUpper;
459         mixin(importMember!module_(member));
460 
461         enum prefix = "test";
462         enum minSize = prefix.length + 1;
463 
464         static if(member.length >= minSize && member[0 .. prefix.length] == prefix &&
465                   isUpper(member[prefix.length])) {
466             enum hasTestPrefix = true;
467         } else {
468             enum hasTestPrefix = false;
469         }
470     }
471 
472     return moduleTestData!(module_, isTestFunction, createFuncTestData);
473 }
474 
475 private TestData[] createFuncTestData(alias module_, string moduleMember)() {
476     mixin(importMember!module_(moduleMember));
477     /*
478       Get all the test functions for this module member. There might be more than one
479       when using parametrized unit tests.
480 
481       Examples:
482       ------
483       void testFoo() {} // -> the array contains one element, testFoo
484       @(1, 2, 3) void testBar(int) {} // The array contains 3 elements, one for each UDA value
485       @Types!(int, float) void testBaz(T)() {} //The array contains 2 elements, one for each type
486       ------
487     */
488     // if the predicate returned true (which is always the case here), then it's either
489     // a regular function or a templated one. If regular we can get a pointer to it
490     enum isRegularFunction = __traits(compiles, &__traits(getMember, module_, moduleMember));
491 
492     static if(isRegularFunction) {
493 
494         enum func = &__traits(getMember, module_, moduleMember);
495         enum arity = arity!func;
496 
497         static if(arity == 0)
498             // the reason we're creating a lambda to call the function is that test functions
499             // are ordinary functions, but we're storing delegates
500             return [ memberTestData!(module_, moduleMember)(() { func(); }) ]; //simple case, just call the function
501         else {
502 
503             // the function has parameters, check if it has UDAs for value parameters to be passed to it
504             alias params = Parameters!func;
505 
506             import std.range: iota;
507             import std.algorithm: any;
508             import std.typecons: tuple, Tuple;
509 
510             bool hasAttributesForAllParams() {
511                 auto ret = true;
512                 foreach(p; params) {
513                     if(tuple(GetAttributes!(module_, moduleMember, p)).length == 0) {
514                         ret = false;
515                     }
516                 }
517                 return ret;
518             }
519 
520             static if(!hasAttributesForAllParams) {
521                 import std.conv: text;
522                 pragma(msg, text("Warning: ", moduleMember, " passes the criteria for a value-parameterized test function",
523                                  " but doesn't have the appropriate value UDAs.\n",
524                                  "         Consider changing its name or annotating it with @DontTest"));
525                 return [];
526             } else {
527 
528                 static if(arity == 1) {
529                     // bind a range of tuples to prod just as cartesianProduct returns
530                     enum prod = [GetAttributes!(module_, moduleMember, params[0])].map!(a => tuple(a));
531                 } else {
532                     import std.conv: text;
533 
534                     mixin(`enum prod = cartesianProduct(` ~ params.length.iota.map!
535                           (a => `[GetAttributes!(module_, moduleMember, params[` ~ guaranteedToString(a) ~ `])]`).join(", ") ~ `);`);
536                 }
537 
538                 TestData[] testData;
539                 foreach(comb; aliasSeqOf!prod) {
540                     enum valuesName = valuesName(comb);
541 
542                     static if(HasAttribute!(module_, moduleMember, AutoTags))
543                         enum extraTags = valuesName.split(".").array;
544                     else
545                         enum string[] extraTags = [];
546 
547 
548                     testData ~= memberTestData!(module_, moduleMember, extraTags)(
549                         // func(value0, value1, ...)
550                         () { func(comb.expand); },
551                         valuesName);
552                 }
553 
554                 return testData;
555             }
556         }
557     } else static if(HasTypes!(mixin(moduleMember))) { //template function with @Types
558         alias types = GetTypes!(mixin(moduleMember));
559         TestData[] testData;
560         foreach(type; types) {
561 
562             static if(HasAttribute!(module_, moduleMember, AutoTags))
563                 enum extraTags = [type.stringof];
564             else
565                 enum string[] extraTags = [];
566 
567             static if(!__traits(compiles, mixin(moduleMember ~ `!(` ~ type.stringof ~ `)()`))) {
568                 pragma(msg, "Could not compile Type-parameterized test for T = ", type);
569                 void _failFunc() {
570                     mixin(moduleMember ~ `!(` ~ type.stringof ~ `)();`);
571                 }
572                 static assert(false);
573             }
574 
575             testData ~= memberTestData!(module_, moduleMember, extraTags)(
576                 () {
577                     mixin(moduleMember ~ `!(` ~ type.stringof ~ `)();`);
578                 },
579                 type.stringof);
580         }
581         return testData;
582     } else {
583         return [];
584     }
585 }
586 
587 
588 
589 // this funtion returns TestData for either classes or test functions
590 // built-in unittest modules are handled by moduleUnitTests
591 // pred determines what qualifies as a test
592 // createTestData must return TestData[]
593 private TestData[] moduleTestData(alias module_, alias pred, alias createTestData)() pure {
594     mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible
595     TestData[] testData;
596     foreach(moduleMember; __traits(allMembers, module_)) {
597 
598         static if(PassesTestPred!(module_, pred, moduleMember))
599             testData ~= createTestData!(module_, moduleMember);
600     }
601 
602     return testData;
603 
604 }
605 
606 // TestData for a member of a module (either a test function or test class)
607 private TestData memberTestData(alias module_, string moduleMember, string[] extraTags = [])
608     (TestFunction testFunction = null, string suffix = "") {
609 
610     mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible
611 
612     immutable singleThreaded = HasAttribute!(module_, moduleMember, Serial);
613     enum builtin = false;
614     enum tags = tagsFromAttrs!(GetAttributes!(module_, moduleMember, Tags));
615     enum exceptionTypeInfo = getExceptionTypeInfo!(mixin(moduleMember));
616 
617     return TestData(fullyQualifiedName!module_~ "." ~ moduleMember,
618                     testFunction,
619                     HasAttribute!(module_, moduleMember, HiddenTest),
620                     HasAttribute!(module_, moduleMember, ShouldFail) || hasUtUDA!(mixin(moduleMember), ShouldFailWith),
621                     singleThreaded,
622                     builtin,
623                     suffix,
624                     tags ~ extraTags,
625                     exceptionTypeInfo);
626 }
627 
628 string[] tagsFromAttrs(T...)() {
629     static assert(T.length <= 1, "@Tags can only be applied once");
630     static if(T.length)
631         return T[0].values;
632     else
633         return [];
634 }
635 
636 version(unittest) {
637 
638     import unit_threaded.tests.module_with_tests; //defines tests and non-tests
639     import unit_threaded.asserts;
640     import std.algorithm;
641     import std.array;
642 
643     //helper function for the unittest blocks below
644     private auto addModPrefix(string[] elements,
645                               string module_ = "unit_threaded.tests.module_with_tests") nothrow {
646         return elements.map!(a => module_ ~ "." ~ a).array;
647     }
648 }
649 
650 unittest {
651     const expected = addModPrefix([ "FooTest", "BarTest", "Blergh"]);
652     const actual = moduleTestClasses!(unit_threaded.tests.module_with_tests).
653         map!(a => a.name).array;
654     assertEqual(actual, expected);
655 }
656 
657 unittest {
658     const expected = addModPrefix([ "testFoo", "testBar", "funcThatShouldShowUpCosOfAttr"]);
659     const actual = moduleTestFunctions!(unit_threaded.tests.module_with_tests).
660         map!(a => a.getPath).array;
661     assertEqual(actual, expected);
662 }
663 
664 
665 unittest {
666     const expected = addModPrefix(["unittest0", "unittest1", "myUnitTest",
667                                    "StructWithUnitTests.InStruct", "StructWithUnitTests.unittest1"]);
668     const actual = moduleUnitTests!(unit_threaded.tests.module_with_tests).
669         map!(a => a.name).array;
670     assertEqual(actual, expected);
671 }
672 
673 version(unittest) {
674     import unit_threaded.testcase: TestCase;
675     private void assertFail(TestCase test, string file = __FILE__, size_t line = __LINE__) {
676         import core.exception;
677         import std.conv;
678 
679         test.silence;
680         assert(test() != [],
681                file ~ ":" ~ line.to!string ~ " Expected test case " ~ test.getPath ~
682                " to fail but it didn't");
683     }
684 
685     private void assertPass(TestCase test, string file = __FILE__, size_t line = __LINE__) {
686         import unit_threaded.should: fail;
687         if(test() != [])
688             fail("'" ~ test.getPath ~ "' was expected to pass but failed", file, line);
689     }
690 }
691 
692 @("Test that parametrized value tests work")
693 unittest {
694     import unit_threaded.factory;
695     import unit_threaded.testcase;
696     import unit_threaded.tests.parametrized;
697 
698     const testData = allTestData!(unit_threaded.tests.parametrized).
699         filter!(a => a.name.endsWith("testValues")).array;
700 
701     auto tests = createTestCases(testData);
702     assertEqual(tests.length, 3);
703 
704     // the first and third test should pass, the second should fail
705     assertPass(tests[0]);
706     assertPass(tests[2]);
707 
708     assertFail(tests[1]);
709 }
710 
711 
712 @("Test that parametrized type tests work")
713 unittest {
714     import unit_threaded.factory;
715     import unit_threaded.testcase;
716     import unit_threaded.tests.parametrized;
717 
718     const testData = allTestData!(unit_threaded.tests.parametrized).
719         filter!(a => a.name.endsWith("testTypes")).array;
720     const expected = addModPrefix(["testTypes.float", "testTypes.int"],
721                                   "unit_threaded.tests.parametrized");
722     const actual = testData.map!(a => a.getPath).array;
723     assertEqual(actual, expected);
724 
725     auto tests = createTestCases(testData);
726     assertEqual(tests.map!(a => a.getPath).array, expected);
727 
728     assertPass(tests[1]);
729     assertFail(tests[0]);
730 }
731 
732 @("Value parametrized built-in unittests")
733 unittest {
734     import unit_threaded.factory;
735     import unit_threaded.testcase;
736     import unit_threaded.tests.parametrized;
737 
738     const testData = allTestData!(unit_threaded.tests.parametrized).
739         filter!(a => a.name.canFind("builtinIntValues")).array;
740 
741     auto tests = createTestCases(testData);
742     assertEqual(tests.length, 4);
743 
744     // these should be ok
745     assertPass(tests[1]);
746 
747     //these should fail
748     assertFail(tests[0]);
749     assertFail(tests[2]);
750     assertFail(tests[3]);
751 }
752 
753 
754 @("Tests can be selected by tags") unittest {
755     import unit_threaded.factory;
756     import unit_threaded.testcase;
757     import unit_threaded.tests.tags;
758 
759     const testData = allTestData!(unit_threaded.tests.tags).array;
760     auto testsNoTags = createTestCases(testData);
761     assertEqual(testsNoTags.length, 4);
762     assertPass(testsNoTags[0]);
763     assertFail(testsNoTags.find!(a => a.getPath.canFind("unittest1")).front);
764     assertFail(testsNoTags[2]);
765     assertFail(testsNoTags[3]);
766 
767     auto testsNinja = createTestCases(testData, ["@ninja"]);
768     assertEqual(testsNinja.length, 1);
769     assertPass(testsNinja[0]);
770 
771     auto testsMake = createTestCases(testData, ["@make"]);
772     assertEqual(testsMake.length, 3);
773     assertPass(testsMake.find!(a => a.getPath.canFind("testMake")).front);
774     assertPass(testsMake.find!(a => a.getPath.canFind("unittest0")).front);
775     assertFail(testsMake.find!(a => a.getPath.canFind("unittest2")).front);
776 
777     auto testsNotNinja = createTestCases(testData, ["~@ninja"]);
778     assertEqual(testsNotNinja.length, 3);
779     assertPass(testsNotNinja.find!(a => a.getPath.canFind("testMake")).front);
780     assertFail(testsNotNinja.find!(a => a.getPath.canFind("unittest1")).front);
781     assertFail(testsNotNinja.find!(a => a.getPath.canFind("unittest2")).front);
782 
783     assertEqual(createTestCases(testData, ["unit_threaded.tests.tags.testMake", "@ninja"]).length, 0);
784 }
785 
786 @("Parametrized built-in tests with @AutoTags get tagged by value")
787 unittest {
788     import unit_threaded.factory;
789     import unit_threaded.testcase;
790     import unit_threaded.tests.parametrized;
791 
792     const testData = allTestData!(unit_threaded.tests.parametrized).
793         filter!(a => a.name.canFind("builtinIntValues")).array;
794 
795     auto two = createTestCases(testData, ["@2"]);
796 
797     assertEqual(two.length, 1);
798     assertFail(two[0]);
799 
800     auto three = createTestCases(testData, ["@3"]);
801     assertEqual(three.length, 1);
802     assertPass(three[0]);
803 }
804 
805 @("Value parametrized function tests with @AutoTags get tagged by value")
806 unittest {
807     import unit_threaded.factory;
808     import unit_threaded.testcase;
809     import unit_threaded.tests.parametrized;
810 
811     const testData = allTestData!(unit_threaded.tests.parametrized).
812         filter!(a => a.name.canFind("testValues")).array;
813 
814     auto two = createTestCases(testData, ["@2"]);
815     assertEqual(two.length, 1);
816     assertFail(two[0]);
817 }
818 
819 @("Type parameterized tests with @AutoTags get tagged by type")
820 unittest {
821     import unit_threaded.factory;
822     import unit_threaded.testcase;
823     import unit_threaded.tests.parametrized;
824 
825     const testData = allTestData!(unit_threaded.tests.parametrized).
826         filter!(a => a.name.canFind("testTypes")).array;
827 
828     auto tests = createTestCases(testData, ["@int"]);
829     assertEqual(tests.length, 1);
830     assertPass(tests[0]);
831 }
832 
833 @("Cartesian parameterized built-in values") unittest {
834     import unit_threaded.factory;
835     import unit_threaded.testcase;
836     import unit_threaded.should: shouldBeSameSetAs;
837     import unit_threaded.tests.parametrized;
838 
839     const testData = allTestData!(unit_threaded.tests.parametrized).
840         filter!(a => a.name.canFind("cartesianBuiltinNoAutoTags")).array;
841 
842     auto tests = createTestCases(testData);
843     tests.map!(a => a.getPath).array.shouldBeSameSetAs(
844                 addModPrefix(["foo.red", "foo.blue", "foo.green", "bar.red", "bar.blue", "bar.green"].
845                              map!(a => "cartesianBuiltinNoAutoTags." ~ a).array,
846                              "unit_threaded.tests.parametrized"));
847     assertEqual(tests.length, 6);
848 
849     auto fooRed = tests.find!(a => a.getPath.canFind("foo.red")).front;
850     assertPass(fooRed);
851     assertEqual(getValue!(string, 0), "foo");
852     assertEqual(getValue!(string, 1), "red");
853     assertEqual(testData.find!(a => a.getPath.canFind("foo.red")).front.tags, []);
854 
855     auto barGreen = tests.find!(a => a.getPath.canFind("bar.green")).front;
856     assertFail(barGreen);
857     assertEqual(getValue!(string, 0), "bar");
858     assertEqual(getValue!(string, 1), "green");
859 
860     assertEqual(testData.find!(a => a.getPath.canFind("bar.green")).front.tags, []);
861     assertEqual(allTestData!(unit_threaded.tests.parametrized).
862                 filter!(a => a.name.canFind("cartesianBuiltinAutoTags")).array.
863                 find!(a => a.getPath.canFind("bar.green")).front.tags,
864                 ["bar", "green"]);
865 }
866 
867 @("Cartesian parameterized function values") unittest {
868     import unit_threaded.factory;
869     import unit_threaded.testcase;
870     import unit_threaded.should: shouldBeSameSetAs;
871 
872     const testData = allTestData!(unit_threaded.tests.parametrized).
873         filter!(a => a.name.canFind("CartesianFunction")).array;
874 
875     auto tests = createTestCases(testData);
876         tests.map!(a => a.getPath).array.shouldBeSameSetAs(
877             addModPrefix(["1.foo", "1.bar", "2.foo", "2.bar", "3.foo", "3.bar"].
878                              map!(a => "testCartesianFunction." ~ a).array,
879                              "unit_threaded.tests.parametrized"));
880 
881     foreach(test; tests) {
882         test.getPath.canFind("2.bar")
883             ? assertPass(test)
884             : assertFail(test);
885     }
886 
887     assertEqual(testData.find!(a => a.getPath.canFind("2.bar")).front.tags,
888                 ["2", "bar"]);
889 
890 }
891 
892 @("module setup and shutdown")
893 unittest {
894     import unit_threaded.testcase;
895     import unit_threaded.factory;
896     import unit_threaded.tests.module_with_setup: gNumBefore, gNumAfter;
897 
898     const testData = allTestData!"unit_threaded.tests.module_with_setup".array;
899     auto tests = createTestCases(testData);
900     assertEqual(tests.length, 2);
901 
902     assertPass(tests[0]);
903     assertEqual(gNumBefore, 1);
904     assertEqual(gNumAfter, 1);
905 
906     assertFail(tests[1]);
907     assertEqual(gNumBefore, 2);
908     assertEqual(gNumAfter, 2);
909 }
910 
911 @("issue 33") unittest {
912     import unit_threaded.factory;
913     import unit_threaded.testcase;
914     import test.issue33;
915 
916     const testData = allTestData!"test.issue33";
917     assertEqual(testData.length, 1);
918 }
919 
920 @("issue 43") unittest {
921     import unit_threaded.factory;
922     import unit_threaded.asserts;
923     import unit_threaded.tests.module_with_tests;
924     import std.algorithm: canFind;
925     import std.array: array;
926 
927     const testData = allTestData!"unit_threaded.tests.module_with_tests";
928     assertEqual(testData.canFind!(a => a.getPath.canFind("InStruct" )), true);
929     auto inStructTest = testData
930         .find!(a => a.getPath.canFind("InStruct"))
931         .array
932         .createTestCases[0];
933     assertFail(inStructTest);
934 }
935 
936 @("@DontTest should work for unittest blocks") unittest {
937     import unit_threaded.factory;
938     import unit_threaded.asserts;
939     import unit_threaded.tests.module_with_tests;
940     import std.algorithm: canFind;
941     import std.array: array;
942 
943     const testData = allTestData!"unit_threaded.tests.module_with_attrs";
944     assertEqual(testData.canFind!(a => a.getPath.canFind("DontTestBlock" )), false);
945 }
946 
947 @("@ShouldFail") unittest {
948     import unit_threaded.factory;
949     import unit_threaded.asserts;
950     import unit_threaded.tests.module_with_tests;
951     import std.algorithm: find, canFind;
952     import std.array: array;
953 
954     const testData = allTestData!"unit_threaded.tests.module_with_attrs";
955 
956     auto willFail = testData
957         .filter!(a => a.getPath.canFind("will fail"))
958         .array
959         .createTestCases[0];
960     assertPass(willFail);
961 }
962 
963 
964 @("@ShouldFailWith") unittest {
965     import unit_threaded.factory;
966     import unit_threaded.asserts;
967     import unit_threaded.tests.module_with_attrs;
968     import unit_threaded.should: shouldThrowExactly, UnitTestException;
969     import std.algorithm: find, canFind;
970     import std.array: array;
971 
972     const testData = allTestData!"unit_threaded.tests.module_with_attrs";
973 
974     auto doesntFail = testData
975         .filter!(a => a.getPath.canFind("ShouldFailWith that fails due to not failing"))
976         .array
977         .createTestCases[0];
978     assertFail(doesntFail);
979 
980     auto wrongType = testData
981         .find!(a => a.getPath.canFind("ShouldFailWith that fails due to wrong type"))
982         .array
983         .createTestCases[0];
984     assertFail(wrongType);
985 
986    auto passes = testData
987         .find!(a => a.getPath.canFind("ShouldFailWith that passes"))
988         .array
989         .createTestCases[0];
990     assertPass(passes);
991 }