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