1 /**
2  * Compile-time reflection to find unittests and properties specified
3  * via UDAs.
4  */
5 
6 module unit_threaded.reflection;
7 
8 import unit_threaded.attrs;
9 import std.traits : fullyQualifiedName, isSomeString, hasUDA;
10 import std.typetuple : Filter;
11 
12 /**
13  * Unit test function type.
14  */
15 alias TestFunction = void function();
16 
17 /**
18  * Unit test data
19  */
20 struct TestData
21 {
22     string name; /// The name of the unit test
23     TestFunction testFunction; /// The test function to call
24     bool hidden; /// If the test is hidden (i.e. will not run by default)
25     bool shouldFail; /// If the test is expected to fail
26     bool serial; /// If the test must be run serially
27 }
28 
29 /**
30  * Finds all unittest blocks in the given modules.
31  * Template parameters are module symbols or their string representation.
32  * Examples:
33  * -----
34  * import my.test.module;
35  * auto testData = allTestData!(my.test.module, "other.test.module");
36  * -----
37  */
38 TestData[] allTestData(Modules...)() @safe pure nothrow
39 {
40     TestData[] testData;
41 
42     foreach (module_; Modules)
43     {
44         static if (is(typeof(module_)) && isSomeString!(typeof(module_)))
45         {
46             //string, generate the code
47             mixin("import " ~ module_ ~ ";");
48             testData ~= moduleTestData!(mixin(module_));
49         }
50         else
51         {
52             //module symbol, just add normally
53             testData ~= moduleTestData!(module_);
54         }
55     }
56 
57     return testData;
58 }
59 
60 /**
61  * Finds all built-in unittest blocks in the given module.
62  * Params:
63  *   module_ = The module to reflect on. Can be a symbol or a string.
64  * Returns: An array of TestData structs
65  */
66 TestData[] moduleTestData(alias module_)() @safe pure nothrow
67 {
68 
69     // Return a name for a unittest block. If no @name UDA is found a name is
70     // created automatically, else the UDA is used.
71     string unittestName(alias test, int index)() @safe nothrow
72     {
73         mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible
74 
75         enum isName(alias T) = is(typeof(T)) && is(typeof(T) == name);
76         alias names = Filter!(isName, __traits(getAttributes, test));
77         static assert(names.length == 0 || names.length == 1,
78             "Found multiple @name UDAs on unittest");
79         enum prefix = fullyQualifiedName!module_ ~ ".";
80 
81         static if (names.length == 1)
82         {
83             return prefix ~ names[0].value;
84         }
85         else
86         {
87             import std.conv;
88 
89             return prefix ~ "unittest" ~ index.to!string;
90         }
91     }
92 
93     TestData[] testData;
94     foreach (index, test; __traits(getUnitTests, module_))
95     {
96         testData ~= TestData(unittestName!(test, index), &test,
97             hasUDA!(test, hiddenTest), hasUDA!(test, shouldFail),
98             hasUDA!(test, serial),);
99     }
100     return testData;
101 }