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 }