1 module unit_threaded.list; 2 3 import std.traits; 4 import std.uni; 5 import std.typetuple; 6 import unit_threaded.check; //enum labels 7 8 /** 9 * Common data for test functions and test classes 10 */ 11 alias void function() TestFunction; 12 struct TestData { 13 string name; 14 bool hidden; 15 bool shouldFail; 16 TestFunction test; //only used for functions, null for classes 17 bool singleThreaded; 18 bool builtin; 19 } 20 21 /** 22 * Finds all test classes (classes implementing a test() function) 23 * in the given module 24 */ 25 auto getTestClasses(alias mod)() pure nothrow { 26 return getTestCases!(mod, isTestClass); 27 } 28 29 /** 30 * Finds all test functions in the given module. 31 * Returns an array of TestData structs 32 */ 33 auto getTestFunctions(alias mod)() pure nothrow { 34 return getTestCases!(mod, isTestFunction); 35 } 36 37 /** 38 * Finds all built-in unittest blocks in the given module. 39 * @return An array of TestData structs 40 */ 41 auto getBuiltinTests(alias mod)() pure nothrow { 42 import std.conv; 43 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 44 TestData[] testData; 45 int index; 46 foreach(test; __traits(getUnitTests, mod)) { 47 enum hidden = false; 48 enum shouldFail = false; 49 enum singleThreaded = false; 50 enum builtin = true; 51 try { 52 testData ~= TestData(fullyQualifiedName!mod ~ ".unittest" ~ (++index).to!string, 53 hidden, shouldFail, &test, singleThreaded, builtin); 54 } catch(Throwable) { 55 assert(false, text("Error converting ", index, " to string")); 56 } 57 } 58 return testData; 59 } 60 61 private template HasAttribute(alias mod, string T, alias A) { 62 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 63 enum index = staticIndexOf!(A, __traits(getAttributes, mixin(T))); 64 static if(index >= 0) { 65 enum HasAttribute = true; 66 } else { 67 enum HasAttribute = false; 68 } 69 } 70 71 72 private template HasHidden(alias mod, string member) { 73 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 74 alias attrs = Filter!(isAHiddenStruct, __traits(getAttributes, mixin(member))); 75 static assert(attrs.length == 0 || attrs.length == 1, 76 "Maximum number of HiddenTest attributes is 1"); 77 static if(attrs.length == 0) { 78 enum HasHidden = false; 79 } else { 80 enum HasHidden = true; 81 } 82 } 83 84 private template HasShouldFail(alias mod, string member) { 85 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 86 alias attrs = Filter!(isAShouldFailStruct, __traits(getAttributes, mixin(member))); 87 static assert(attrs.length == 0 || attrs.length == 1, 88 "Maximum number of ShouldFail attributes is 1"); 89 static if(attrs.length == 0) { 90 enum HasShouldFail = false; 91 } else { 92 enum HasShouldFail = true; 93 } 94 } 95 96 97 private auto getTestCases(alias mod, alias pred)() pure nothrow { 98 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 99 TestData[] testData; 100 foreach(moduleMember; __traits(allMembers, mod)) { 101 102 enum notPrivate = __traits(compiles, mixin(moduleMember)); //only way I know to check if private 103 104 static if(notPrivate && pred!(mod, moduleMember)) { 105 static if(!HasAttribute!(mod, moduleMember, DontTest)) { 106 testData ~= createTestData!(mod, moduleMember); 107 } 108 } 109 } 110 111 return testData; 112 } 113 114 private auto createTestData(alias mod, string moduleMember)() pure nothrow { 115 TestFunction getTestFunction(alias mod, string moduleMember)() { 116 //returns a function pointer for test functions, null for test classes 117 static if(__traits(compiles, &__traits(getMember, mod, moduleMember))) { 118 return &__traits(getMember, mod, moduleMember); 119 } else { 120 return null; 121 } 122 } 123 124 return TestData(fullyQualifiedName!mod ~ "." ~ moduleMember, 125 HasHidden!(mod, moduleMember), 126 HasShouldFail!(mod, moduleMember), 127 getTestFunction!(mod, moduleMember), 128 HasAttribute!(mod, moduleMember, SingleThreaded)); 129 } 130 131 private template isTestClass(alias mod, string moduleMember) { 132 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 133 static if(__traits(compiles, isAggregateType!(mixin(moduleMember)))) { 134 static if(isAggregateType!(mixin(moduleMember))) { 135 136 enum hasUnitTest = HasAttribute!(mod, moduleMember, UnitTest); 137 enum hasTestMethod = __traits(hasMember, mixin(moduleMember), "test"); 138 139 enum isTestClass = hasTestMethod || hasUnitTest; 140 } else { 141 enum isTestClass = false; 142 } 143 } else { 144 enum isTestClass = false; 145 } 146 } 147 148 149 private template isTestFunction(alias mod, string moduleMember) { 150 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 151 static if(isSomeFunction!(mixin(moduleMember))) { 152 enum isTestFunction = hasTestPrefix!(mod, moduleMember) || 153 HasAttribute!(mod, moduleMember, UnitTest); 154 } else { 155 enum isTestFunction = false; 156 } 157 } 158 159 private template hasTestPrefix(alias mod, alias T) { 160 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 161 162 enum prefix = "test"; 163 enum minSize = prefix.length + 1; 164 165 static if(isSomeFunction!(mixin(T)) && 166 T.length >= minSize && T[0 .. prefix.length] == "test" && 167 isUpper(T[prefix.length])) { 168 enum hasTestPrefix = true; 169 } else { 170 enum hasTestPrefix = false; 171 } 172 } 173 174 175 import unit_threaded.tests.module_with_tests; //defines tests and non-tests 176 import unit_threaded.asserts; 177 import std.algorithm; 178 import std.array; 179 180 //helper function for the unittest blocks below 181 private auto addModPrefix(string[] elements, string mod = "unit_threaded.tests.module_with_tests") nothrow { 182 return elements.map!(a => mod ~ "." ~ a).array; 183 } 184 185 unittest { 186 const expected = addModPrefix([ "FooTest", "BarTest", "Blergh"]); 187 const actual = getTestClasses!(unit_threaded.tests.module_with_tests).map!(a => a.name).array; 188 assertEqual(actual, expected); 189 } 190 191 unittest { 192 static assert(hasTestPrefix!(unit_threaded.tests.module_with_tests, "testFoo")); 193 static assert(!hasTestPrefix!(unit_threaded.tests.module_with_tests, "funcThatShouldShowUpCosOfAttr")); 194 } 195 196 unittest { 197 const expected = addModPrefix([ "testFoo", "testBar", "funcThatShouldShowUpCosOfAttr" ]); 198 const actual = getTestFunctions!(unit_threaded.tests.module_with_tests).map!(a => a.name).array; 199 assertEqual(actual, expected); 200 } 201 202 203 unittest { 204 const expected = addModPrefix(["unittest1", "unittest2"]); 205 const actual = getBuiltinTests!(unit_threaded.tests.module_with_tests).map!(a => a.name).array; 206 assertEqual(actual, expected); 207 }