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