1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.util.UnitTest;
13 
14 import core.time;
15 
16 private enum string[] FixedObjectMembers = ["toString", "opCmp", "opEquals", "Monitor", "factory"];
17 
18 void testUnits(T)() {
19 	enum v = generateUnitTests!T;
20 	// pragma(msg, v);
21 	mixin(v);
22 }
23 
24 string generateUnitTests(T)() {
25 	import std.string;
26 	import std.algorithm;
27 	import std.traits;
28 
29 	enum fullTypeName = fullyQualifiedName!(T);
30 	enum memberModuleName = moduleName!(T);
31 
32 	string[] methodsBefore;
33 	string[] methodsAfter;
34 
35 	string str;
36 	str ~= `import std.stdio; import hunt.logging;
37 writeln("=================================");
38 writeln("Module: ` ~ fullTypeName ~ `     ");
39 writeln("=================================");
40 
41 `;
42 	str ~= "import " ~ memberModuleName ~ ";\n";
43 	str ~= T.stringof ~ " t;\n";
44 
45 	// 
46 	foreach (memberName; __traits(allMembers, T)) {
47 		// pragma(msg, "member: " ~ memberName);
48 		static if (is(T == class) && FixedObjectMembers.canFind(memberName)) {
49 			// pragma(msg, "skipping fixed Object member: " ~ memberName);
50 		} else {
51 			enum memberProtection = __traits(getProtection, __traits(getMember, T, memberName));
52 			static if (memberProtection == "private"
53 					|| memberProtection == "protected" 
54 					|| memberProtection == "export") {
55 				// version (HUNT_DEBUG) pragma(msg, "skip private member: " ~ memberName);
56 			} else {
57 				import std.meta : Alias;
58 				alias currentMember = Alias!(__traits(getMember, T, memberName));
59 				static if (hasUDA!(currentMember, Before)) {
60 					alias memberType = typeof(currentMember);
61 					static if (is(memberType == function)) {
62 						methodsBefore ~= memberName;
63 					}
64 				}
65 
66 				static if (hasUDA!(currentMember, After)) {
67 					alias memberType = typeof(currentMember);
68 					static if (is(memberType == function)) {
69 						methodsAfter ~= memberName;
70 					}
71 				}
72 			}
73 		}
74 	}
75 
76 	// 
77 	foreach (memberName; __traits(allMembers, T)) {
78 	// 	pragma(msg, "member: " ~ memberName);
79 		static if (is(T == class) && FixedObjectMembers.canFind(memberName)) {
80 			// pragma(msg, "skipping fixed Object member: " ~ memberName);
81 		} else {
82 			enum memberProtection = __traits(getProtection, __traits(getMember, T, memberName));
83 			static if (memberProtection == "private"
84 					|| memberProtection == "protected" 
85 					|| memberProtection == "export") {
86 				// version (HUNT_DEBUG) pragma(msg, "skip private member: " ~ memberName);
87 			} else {
88 				import std.meta : Alias;
89 				alias currentMember = Alias!(__traits(getMember, T, memberName));
90 
91 				static if (isFunction!(currentMember)) {
92 					alias testWithUDAs = getUDAs!(currentMember, TestWith);// hasUDA!(currentMember, Test);
93 					static if(testWithUDAs.length >0) {
94 						str ~= `writeln("\n========> testing: ` ~ memberName ~ "\");\n";
95 
96 						// Every @Test method will be test alone. 
97 						str ~= "t = new " ~ T.stringof ~ "();\n";
98 
99 						// running methods annotated with BEFORE
100 						foreach(string s; methodsBefore) {
101 							str ~= "t." ~ s ~ "();\n";
102 						}
103 						
104 						// execute a test 
105 						alias expectedType = typeof(testWithUDAs[0].expected);
106 						static if(is(expectedType : Throwable)) {
107 							str ~= "try { t." ~ memberName ~ "(); } catch(" ~ fullyQualifiedName!expectedType ~ 
108 								" ex) { version(HUNT_DEBUG) { warning(ex.msg); } }\n";
109 						} else {
110 							str ~= "t." ~ memberName ~ "();\n"; 
111 						}
112 
113 						// running methods annotated with BEFORE
114 						foreach(string s; methodsAfter) {
115 							str ~= "t." ~ s ~ "();\n";
116 						}
117 					} else {
118 						static if (memberName.startsWith("test") || memberName.endsWith("Test")
119 							|| hasUDA!(currentMember, Test)) {
120 							str ~= `writeln("\n========> testing: ` ~ memberName ~ "\");\n";
121 
122 							// Every @Test method will be test alone. 
123 							str ~= "t = new " ~ T.stringof ~ "();\n";
124 
125 							// running methods annotated with BEFORE
126 							foreach(string s; methodsBefore) {
127 								str ~= "t." ~ s ~ "();\n";
128 							}
129 							
130 							// execute a test 
131 							str ~= "t." ~ memberName ~ "();\n"; 
132 
133 							// running methods annotated with BEFORE
134 							foreach(string s; methodsAfter) {
135 								str ~= "t." ~ s ~ "();\n";
136 							}
137 						}
138 					}
139 				}
140 			}
141 		}
142 	}
143 	return str;
144 }
145 
146 /**
147 */
148 struct TestWith(T = Object) {
149 	T expected; 
150 }
151 
152 
153 /**
154  * The <code>Test</code> annotation tells JUnit that the <code>public void</code> method
155  * to which it is attached can be run as a test case. To run the method,
156  * JUnit first constructs a fresh instance of the class then invokes the
157  * annotated method. Any exceptions thrown by the test will be reported
158  * by JUnit as a failure. If no exceptions are thrown, the test is assumed
159  * to have succeeded.
160  * <p>
161  * A simple test looks like this:
162  * <pre>
163  * public class Example {
164  *    <b>&#064;Test</b>
165  *    public void method() {
166  *       org.junit.Assert.assertTrue( new ArrayList().isEmpty() );
167  *    }
168  * }
169  * </pre>
170  * <p>
171  * The <code>Test</code> annotation supports two optional parameters.
172  * The first, <code>expected</code>, declares that a test method should throw
173  * an exception. If it doesn't throw an exception or if it throws a different exception
174  * than the one declared, the test fails. For example, the following test succeeds:
175  * <pre>
176  *    &#064;Test(<b>expected=IndexOutOfBoundsException.class</b>) public void outOfBounds() {
177  *       new ArrayList&lt;Object&gt;().get(1);
178  *    }
179  * </pre>
180  * If the exception's message or one of its properties should be verified, the
181  * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used. Further
182  * information about exception testing can be found at the
183  * <a href="https://github.com/junit-team/junit/wiki/Exception-testing">JUnit Wiki</a>.
184  * <p>
185  * The second optional parameter, <code>timeout</code>, causes a test to fail if it takes
186  * longer than a specified amount of clock time (measured in milliseconds). The following test fails:
187  * <pre>
188  *    &#064;Test(<b>timeout=100</b>) public void infinity() {
189  *       while(true);
190  *    }
191  * </pre>
192  * <b>Warning</b>: while <code>timeout</code> is useful to catch and terminate
193  * infinite loops, it should <em>not</em> be considered deterministic. The
194  * following test may or may not fail depending on how the operating system
195  * schedules threads:
196  * <pre>
197  *    &#064;Test(<b>timeout=100</b>) public void sleep100() {
198  *       Thread.sleep(100);
199  *    }
200  * </pre>
201  * <b>THREAD SAFETY WARNING:</b> Test methods with a timeout parameter are run in a thread other than the
202  * thread which runs the fixture's @Before and @After methods. This may yield different behavior for
203  * code that is not thread safe when compared to the same test method without a timeout parameter.
204  * <b>Consider using the {@link org.junit.rules.Timeout} rule instead</b>, which ensures a test method is run on the
205  * same thread as the fixture's @Before and @After methods.
206  *
207  */
208 struct Test {
209 	Duration timeout;
210 }
211 
212 
213 /**
214  * When writing tests, it is common to find that several tests need similar
215  * objects created before they can run. Annotating a <code>public void</code> method
216  * with <code>&#064;Before</code> causes that method to be run before the {@link org.junit.Test} method.
217  * The <code>&#064;Before</code> methods of superclasses will be run before those of the current class,
218  * unless they are overridden in the current class. No other ordering is defined.
219  * <p>
220  * Here is a simple example:
221  * <pre>
222  * public class Example {
223  *    List empty;
224  *    &#064;Before public void initialize() {
225  *       empty= new ArrayList();
226  *    }
227  *    &#064;Test public void size() {
228  *       ...
229  *    }
230  *    &#064;Test public void remove() {
231  *       ...
232  *    }
233  * }
234  * </pre>
235  *
236  */
237 interface Before {
238 }
239 
240 
241 /**
242  * If you allocate external resources in a {@link org.junit.Before} method you need to release them
243  * after the test runs. Annotating a <code>public void</code> method
244  * with <code>&#064;After</code> causes that method to be run after the {@link org.junit.Test} method. All <code>&#064;After</code>
245  * methods are guaranteed to run even if a {@link org.junit.Before} or {@link org.junit.Test} method throws an
246  * exception. The <code>&#064;After</code> methods declared in superclasses will be run after those of the current
247  * class, unless they are overridden in the current class.
248  * <p>
249  * Here is a simple example:
250  * <pre>
251  * public class Example {
252  *    File output;
253  *    &#064;Before public void createOutputFile() {
254  *          output= new File(...);
255  *    }
256  *    &#064;Test public void something() {
257  *          ...
258  *    }
259  *    &#064;After public void deleteOutputFile() {
260  *          output.delete();
261  *    }
262  * }
263  * </pre>
264  *
265  */
266 interface After {
267 }
268 
269 
270 /**
271  * Sometimes several tests need to share computationally expensive setup
272  * (like logging into a database). While this can compromise the independence of
273  * tests, sometimes it is a necessary optimization. Annotating a <code>public static void</code> no-arg method
274  * with <code>@BeforeClass</code> causes it to be run once before any of
275  * the test methods in the class. The <code>@BeforeClass</code> methods of superclasses
276  * will be run before those of the current class, unless they are shadowed in the current class.
277  * <p>
278  * For example:
279  * <pre>
280  * public class Example {
281  *    &#064;BeforeClass public static void onlyOnce() {
282  *       ...
283  *    }
284  *    &#064;Test public void one() {
285  *       ...
286  *    }
287  *    &#064;Test public void two() {
288  *       ...
289  *    }
290  * }
291  * </pre>
292  *
293  */
294 interface BeforeClass {
295 
296 }
297 
298 
299 /**
300  * If you allocate expensive external resources in a {@link org.junit.BeforeClass} method you need to release them
301  * after all the tests in the class have run. Annotating a <code>public static void</code> method
302  * with <code>&#064;AfterClass</code> causes that method to be run after all the tests in the class have been run. All <code>&#064;AfterClass</code>
303  * methods are guaranteed to run even if a {@link org.junit.BeforeClass} method throws an
304  * exception. The <code>&#064;AfterClass</code> methods declared in superclasses will be run after those of the current
305  * class, unless they are shadowed in the current class.
306  * <p>
307  * Here is a simple example:
308  * <pre>
309  * public class Example {
310  *    private static DatabaseConnection database;
311  *    &#064;BeforeClass public static void login() {
312  *          database= ...;
313  *    }
314  *    &#064;Test public void something() {
315  *          ...
316  *    }
317  *    &#064;Test public void somethingElse() {
318  *          ...
319  *    }
320  *    &#064;AfterClass public static void logout() {
321  *          database.logout();
322  *    }
323  * }
324  * </pre>
325  *
326  */
327 interface AfterClass {
328 
329 }
330 
331 
332 /**
333  * Sometimes you want to temporarily disable a test or a group of tests. Methods annotated with
334  * {@link org.junit.Test} that are also annotated with <code>&#064;Ignore</code> will not be executed as tests.
335  * Also, you can annotate a class containing test methods with <code>&#064;Ignore</code> and none of the containing
336  * tests will be executed. Native JUnit 4 test runners should report the number of ignored tests along with the
337  * number of tests that ran and the number of tests that failed.
338  *
339  * <p>For example:
340  * <pre>
341  *    &#064;Ignore &#064;Test public void something() { ...
342  * </pre>
343  * &#064;Ignore takes an optional default parameter if you want to record why a test is being ignored:
344  * <pre>
345  *    &#064;Ignore("not ready yet") &#064;Test public void something() { ...
346  * </pre>
347  * &#064;Ignore can also be applied to the test class:
348  * <pre>
349  *      &#064;Ignore public class IgnoreMe {
350  *          &#064;Test public void test1() { ... }
351  *          &#064;Test public void test2() { ... }
352  *         }
353  * </pre>
354  *
355  */
356 struct Ignore {
357     /**
358      * The optional reason why the test is ignored.
359      */	
360 	string value;
361 }