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.ConsoleLogger; 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>@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 * @Test(<b>expected=IndexOutOfBoundsException.class</b>) public void outOfBounds() { 177 * new ArrayList<Object>().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 * @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 * @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>@Before</code> causes that method to be run before the {@link org.junit.Test} method. 217 * The <code>@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 * @Before public void initialize() { 225 * empty= new ArrayList(); 226 * } 227 * @Test public void size() { 228 * ... 229 * } 230 * @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>@After</code> causes that method to be run after the {@link org.junit.Test} method. All <code>@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>@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 * @Before public void createOutputFile() { 254 * output= new File(...); 255 * } 256 * @Test public void something() { 257 * ... 258 * } 259 * @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 * @BeforeClass public static void onlyOnce() { 282 * ... 283 * } 284 * @Test public void one() { 285 * ... 286 * } 287 * @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>@AfterClass</code> causes that method to be run after all the tests in the class have been run. All <code>@AfterClass</code> 303 * methods are guaranteed to run even if a {@link org.junit.BeforeClass} method throws an 304 * exception. The <code>@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 * @BeforeClass public static void login() { 312 * database= ...; 313 * } 314 * @Test public void something() { 315 * ... 316 * } 317 * @Test public void somethingElse() { 318 * ... 319 * } 320 * @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>@Ignore</code> will not be executed as tests. 335 * Also, you can annotate a class containing test methods with <code>@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 * @Ignore @Test public void something() { ... 342 * </pre> 343 * @Ignore takes an optional default parameter if you want to record why a test is being ignored: 344 * <pre> 345 * @Ignore("not ready yet") @Test public void something() { ... 346 * </pre> 347 * @Ignore can also be applied to the test class: 348 * <pre> 349 * @Ignore public class IgnoreMe { 350 * @Test public void test1() { ... } 351 * @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 }