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