1 /****************************************************************************** 2 3 Defines base exception class thrown by test checks and set of helper 4 functions to define actual test cases. These helpers are supposed to be 5 used in unittest blocks instead of asserts. 6 7 There were three reasons why dedicated function got introduced: 8 9 1) Bultin `assert` throws an `Error`, which makes implementing test runner 10 that doesn't stop on first failure illegal by language specification. 11 2) These `test` functions can provide more informational formatting compared 12 to plain `assert`, for example `test!("==")(a, b)` will print `a` and `b` 13 values on failure. 14 3) Having dedicated exception type for test failures makes possible to 15 distinguish in test runners between contract failures and test failures. 16 17 Copyright: 18 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 19 All rights reserved. 20 21 License: 22 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 23 Alternatively, this file may be distributed under the terms of the Tango 24 3-Clause BSD License (see LICENSE_BSD.txt for details). 25 26 *******************************************************************************/ 27 28 module ocean.core.Test; 29 30 31 import ocean.transition; 32 33 import core.memory; 34 import ocean.core.Enforce; 35 import ocean.text.convert.Formatter; 36 37 /****************************************************************************** 38 39 Exception class to be thrown from unot tests blocks. 40 41 *******************************************************************************/ 42 43 class TestException : Exception 44 { 45 /*************************************************************************** 46 47 wraps parent constructor 48 49 ***************************************************************************/ 50 51 public this ( istring msg, istring file = __FILE__, int line = __LINE__ ) 52 { 53 super( msg, file, line ); 54 } 55 } 56 57 /****************************************************************************** 58 59 Effectively partial specialization alias: 60 test = enforceImpl!(TestException) 61 62 Same arguments as enforceImpl. 63 64 ******************************************************************************/ 65 66 public void test ( T ) ( T ok, cstring msg = "", 67 istring file = __FILE__, int line = __LINE__ ) 68 { 69 if (!msg.length) 70 { 71 msg = "unit test has failed"; 72 } 73 enforceImpl!(TestException, T)(ok, idup(msg), file, line); 74 } 75 76 /****************************************************************************** 77 78 ditto 79 80 ******************************************************************************/ 81 82 public void test ( istring op, T1, T2 ) ( T1 a, 83 T2 b, istring file = __FILE__, int line = __LINE__ ) 84 { 85 enforceImpl!(op, TestException)(a, b, file, line); 86 } 87 88 unittest 89 { 90 try 91 { 92 test(false); 93 assert(false); 94 } 95 catch (TestException e) 96 { 97 assert(e.message() == "unit test has failed"); 98 assert(e.line == __LINE__ - 6); 99 } 100 101 try 102 { 103 test!("==")(2, 3); 104 assert(false); 105 } 106 catch (TestException e) 107 { 108 assert(e.message() == "expression '2 == 3' evaluates to false"); 109 assert(e.line == __LINE__ - 6); 110 } 111 } 112 113 /****************************************************************************** 114 115 Verifies that given expression throws exception instance of expected type. 116 117 Params: 118 E = exception type to expect, Exception by default 119 expr = expression that is expected to throw during evaluation 120 strict = if 'true', accepts only exact exception type, disallowing 121 polymorphic conversion 122 file = file of origin 123 line = line of origin 124 125 Throws: 126 `TestException` if nothing has been thrown from `expr` 127 Propagates any thrown exception which is not `E` 128 In strict mode (default) also propagates any children of E (disables 129 polymorphic catching) 130 131 ******************************************************************************/ 132 133 public void testThrown ( E : Exception = Exception ) ( lazy void expr, 134 bool strict = true, istring file = __FILE__, int line = __LINE__ ) 135 { 136 bool was_thrown = false; 137 try 138 { 139 expr; 140 } 141 catch (E e) 142 { 143 if (strict) 144 { 145 if (E.classinfo == e.classinfo) 146 { 147 was_thrown = true; 148 } 149 else 150 { 151 throw e; 152 } 153 } 154 else 155 { 156 was_thrown = true; 157 } 158 } 159 160 if (!was_thrown) 161 { 162 throw new TestException( 163 "Expected '" ~ E.stringof ~ "' to be thrown, but it wasn't", 164 file, 165 line 166 ); 167 } 168 } 169 170 unittest 171 { 172 void foo() { throw new Exception(""); } 173 testThrown(foo()); 174 175 void test_foo() { throw new TestException("", "", 0); } 176 testThrown!(TestException)(test_foo()); 177 178 // make sure only exact exception type is caught 179 testThrown!(TestException)( 180 testThrown!(Exception)(test_foo()) 181 ); 182 183 // .. unless strict matching is disabled 184 testThrown!(Exception)(test_foo(), false); 185 } 186 187 /****************************************************************************** 188 189 Utility class useful in scenarios where actual testing code is reused in 190 different contexts and file+line information is not enough to uniquely 191 identify failed case. 192 193 NamedTest is also exception class on its own - when test condition fails 194 it throws itself. 195 196 ******************************************************************************/ 197 198 class NamedTest : TestException 199 { 200 /*************************************************************************** 201 202 Field to store test name this check belongs to. Useful 203 when you have a common verification code reused by different test cases 204 and file+line is not enough for identification. 205 206 ***************************************************************************/ 207 208 private istring name; 209 210 /************************************************************************** 211 212 Constructor 213 214 ***************************************************************************/ 215 216 this(istring name) 217 { 218 super(null); 219 this.name = name; 220 } 221 222 /*************************************************************************** 223 224 message that also uses this.name if present 225 226 ****************************************************************************/ 227 228 static if (is(typeof(Throwable.message))) 229 { 230 public override cstring message ( ) const 231 { 232 if (this.name.length) 233 { 234 return format("[{}] {}", this.name, this.msg); 235 } 236 else 237 { 238 return format("{}", this.msg); 239 } 240 } 241 } 242 243 /************************************************************************** 244 245 Same as enforceImpl!(TestException) but uses this.name for error message 246 formatting. 247 248 ***************************************************************************/ 249 250 public void test ( T ) ( T ok, cstring msg = "", istring file = __FILE__, 251 int line = __LINE__ ) 252 { 253 // uses `enforceImpl` instead of `test` so that pre-constructed 254 // exception instance can be used. 255 if (!msg.length) 256 { 257 msg = "unit test has failed"; 258 } 259 enforceImpl(this, ok, idup(msg), file, line); 260 } 261 262 /************************************************************************** 263 264 Same as enforceImpl!(op, TestException) but uses this.name for error message 265 formatting. 266 267 ***************************************************************************/ 268 269 public void test ( istring op, T1, T2 ) ( T1 a, T2 b, 270 istring file = __FILE__, int line = __LINE__ ) 271 { 272 // uses `enforceImpl` instead of `test` so that pre-constructed 273 // exception instance can be used. 274 enforceImpl!(op)(this, a, b, file, line); 275 } 276 } 277 278 unittest 279 { 280 auto t = new NamedTest("name"); 281 282 t.test(true); 283 284 try 285 { 286 t.test(false); 287 assert(false); 288 } 289 catch (TestException e) 290 { 291 assert(e.message() == "[name] unit test has failed"); 292 } 293 294 try 295 { 296 t.test!(">")(2, 3); 297 assert(false); 298 } 299 catch (TestException e) 300 { 301 assert(e.message() == "[name] expression '2 > 3' evaluates to false"); 302 } 303 } 304 305 /****************************************************************************** 306 307 Verifies that call to `expr` does not allocate GC memory 308 309 This is achieved by checking GC usage stats before and after the call. 310 311 Params: 312 expr = any expression, wrapped in void-returning delegate if necessary 313 file = file where test is invoked 314 line = line where test is invoked 315 316 Throws: 317 TestException if unexpected allocation happens 318 319 ******************************************************************************/ 320 321 public void testNoAlloc ( lazy void expr, istring file = __FILE__, 322 int line = __LINE__ ) 323 { 324 size_t used1, free1; 325 ocean.transition.gc_usage(used1, free1); 326 327 expr(); 328 329 size_t used2, free2; 330 ocean.transition.gc_usage(used2, free2); 331 332 enforceImpl!(TestException, bool)( 333 used1 == used2 && free1 == free2, 334 format("Expression expected to not allocate but GC usage stats have " ~ 335 "changed from {} (used) / {} (free) to {} / {}", 336 used1, free1, used2, free2), 337 file, 338 line 339 ); 340 } 341 342 /// 343 unittest 344 { 345 testNoAlloc({} ()); 346 347 testThrown!(TestException)( 348 testNoAlloc({ auto x = new int; } ()) 349 ); 350 } 351 352 unittest 353 { 354 auto t = new NamedTest("struct"); 355 356 struct S { int a; char[2] arr; } 357 358 try 359 { 360 t.test!("==")(S(1, ['a', 'b']), S(2, ['c', 'd'])); 361 assert(false); 362 } 363 catch (TestException e) 364 { 365 assert(e.message() == `[struct] expression '{ a: 1, arr: "ab" } == { a: 2, arr: "cd" }' evaluates to false`); 366 } 367 } 368 369 unittest 370 { 371 auto t = new NamedTest("typedef"); 372 373 mixin(Typedef!(int, "MyInt")); 374 375 try 376 { 377 t.test!("==")(cast(MyInt)10, cast(MyInt)20); 378 assert(false); 379 } 380 catch (TestException e) 381 { 382 assert(e.message() == `[typedef] expression '10 == 20' evaluates to false`); 383 } 384 }