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 // }