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 private template HasAttribute(alias mod, string T, alias A) { 9 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 10 enum index = staticIndexOf!(A, __traits(getAttributes, mixin(T))); 11 static if(index >= 0) { 12 enum HasAttribute = true; 13 } else { 14 enum HasAttribute = false; 15 } 16 17 } 18 19 /** 20 * Common data for test functions and test classes 21 */ 22 alias void function() TestFunction; 23 struct TestData { 24 immutable string name; 25 immutable bool hidden; 26 const TestFunction test; //only used for functions, null for classes 27 } 28 29 /** 30 * Finds all test classes (classes implementing a test() function) 31 * in the given module 32 */ 33 auto getTestClassNames(alias mod)() pure nothrow { 34 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 35 TestData[] classes; 36 foreach(klass; __traits(allMembers, mod)) { 37 static if(__traits(compiles, mixin(klass)) && !isSomeFunction!(mixin(klass)) && 38 !HasAttribute!(mod, klass, DontTest) && 39 (__traits(hasMember, mixin(klass), "test") || 40 HasAttribute!(mod, klass, UnitTest))) { 41 classes ~= TestData(fullyQualifiedName!mod ~ "." ~ klass, HasAttribute!(mod, klass, HiddenTest)); 42 } 43 } 44 45 return classes; 46 } 47 48 /** 49 * Finds all test functions in the given module. 50 * Returns an array of TestData structs 51 */ 52 auto getTestFunctions(alias mod)() pure nothrow { 53 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 54 TestData[] functions; 55 foreach(moduleMember; __traits(allMembers, mod)) { 56 static if(__traits(compiles, mixin(moduleMember)) && !HasAttribute!(mod, moduleMember, DontTest) && 57 (IsTestFunction!(mod, moduleMember) || 58 (isSomeFunction!(mixin(moduleMember)) && HasAttribute!(mod, moduleMember, UnitTest)))) { 59 enum funcName = fullyQualifiedName!mod ~ "." ~ moduleMember; 60 enum funcAddr = "&" ~ funcName; 61 62 mixin(`functions ~= TestData("` ~ funcName ~ `", ` ~ 63 HasAttribute!(mod, moduleMember, HiddenTest).stringof ~ ", " ~ funcAddr ~ ");"); 64 65 } 66 } 67 68 return functions; 69 } 70 71 private template IsTestFunction(alias mod, alias T) { 72 mixin("import " ~ fullyQualifiedName!mod ~ ";"); //so it's visible 73 74 enum prefix = "test"; 75 enum minSize = prefix.length + 1; 76 77 static if(isSomeFunction!(mixin(T)) && 78 T.length >= minSize && T[0 .. prefix.length] == "test" && 79 isUpper(T[prefix.length])) { 80 enum IsTestFunction = true; 81 } else { 82 enum IsTestFunction = false; 83 } 84 } 85 86 87 //helper function for the unittest blocks below 88 private auto addModule(string[] elements, string mod = "unit_threaded.tests.module_with_tests") nothrow { 89 import std.algorithm; 90 import std.array; 91 return array(map!(a => mod ~ "." ~ a)(elements)); 92 } 93 94 import unit_threaded.tests.module_with_tests; //defines tests and non-tests 95 import unit_threaded.asserts; 96 97 98 unittest { 99 import std.algorithm; 100 import std.array; 101 const expected = addModule([ "FooTest", "BarTest", "Blergh"]); 102 const actual = array(map!(a => a.name)(getTestClassNames!(unit_threaded.tests.module_with_tests)())); 103 assertEqual(actual, expected); 104 } 105 106 unittest { 107 static assert(IsTestFunction!(unit_threaded.tests.module_with_tests, "testFoo")); 108 static assert(!IsTestFunction!(unit_threaded.tests.module_with_tests, "funcThatShouldShowUpCosOfAttr")); 109 } 110 111 unittest { 112 import std.algorithm; 113 import std.array; 114 auto expected = addModule([ "testFoo", "testBar", "funcThatShouldShowUpCosOfAttr" ]); 115 auto actual = map!(a => a.name)(getTestFunctions!(unit_threaded.tests.module_with_tests)()); 116 assertEqual(array(actual), expected); 117 } 118 119 // unittest { 120 // assert(HasHiddenAttr!("unit_threaded.tests.module_with_tests", withHidden)); 121 // assert(!HasHiddenAttr!("unit_threaded.tests.module_with_tests", withoutHidden)); 122 // }