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 ( string msg, string 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 string 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 ( string op, T1, T2 ) ( T1 a, 83 T2 b, string 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, string 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 string name; 209 210 /************************************************************************** 211 212 Constructor 213 214 ***************************************************************************/ 215 216 this(string 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 @trusted nothrow 231 { 232 // The Formatter currently has no annotation, as it would require 233 // extensive work on it (and a new language feature), 234 // but we know it's neither throwing (it doesn't on its own), 235 // nor does it present a non-safe interface. 236 scope (failure) assert(0); 237 238 if (this.name.length) 239 { 240 return format("[{}] {}", this.name, this.msg); 241 } 242 else 243 { 244 return format("{}", this.msg); 245 } 246 } 247 } 248 249 /************************************************************************** 250 251 Same as enforceImpl!(TestException) but uses this.name for error message 252 formatting. 253 254 ***************************************************************************/ 255 256 public void test ( T ) ( T ok, cstring msg = "", string file = __FILE__, 257 int line = __LINE__ ) 258 { 259 // uses `enforceImpl` instead of `test` so that pre-constructed 260 // exception instance can be used. 261 if (!msg.length) 262 { 263 msg = "unit test has failed"; 264 } 265 enforceImpl(this, ok, idup(msg), file, line); 266 } 267 268 /************************************************************************** 269 270 Same as enforceImpl!(op, TestException) but uses this.name for error message 271 formatting. 272 273 ***************************************************************************/ 274 275 public void test ( string op, T1, T2 ) ( T1 a, T2 b, 276 string file = __FILE__, int line = __LINE__ ) 277 { 278 // uses `enforceImpl` instead of `test` so that pre-constructed 279 // exception instance can be used. 280 enforceImpl!(op)(this, a, b, file, line); 281 } 282 } 283 284 unittest 285 { 286 auto t = new NamedTest("name"); 287 288 t.test(true); 289 290 try 291 { 292 t.test(false); 293 assert(false); 294 } 295 catch (TestException e) 296 { 297 assert(e.message() == "[name] unit test has failed"); 298 } 299 300 try 301 { 302 t.test!(">")(2, 3); 303 assert(false); 304 } 305 catch (TestException e) 306 { 307 assert(e.message() == "[name] expression '2 > 3' evaluates to false"); 308 } 309 } 310 311 /****************************************************************************** 312 313 Verifies that call to `expr` does not allocate GC memory 314 315 This is achieved by checking GC usage stats before and after the call. 316 317 Params: 318 expr = any expression, wrapped in void-returning delegate if necessary 319 file = file where test is invoked 320 line = line where test is invoked 321 322 Throws: 323 TestException if unexpected allocation happens 324 325 ******************************************************************************/ 326 327 public void testNoAlloc ( lazy void expr, string file = __FILE__, 328 int line = __LINE__ ) 329 { 330 auto before = GC.stats(); 331 expr(); 332 auto after = GC.stats(); 333 334 enforceImpl!(TestException, bool)( 335 before.usedSize == after.usedSize && before.freeSize == after.freeSize, 336 format("Expression expected to not allocate but GC usage stats have " ~ 337 "changed from {} (used) / {} (free) to {} / {}", 338 before.usedSize, before.freeSize, after.usedSize, after.freeSize), 339 file, 340 line 341 ); 342 } 343 344 /// 345 unittest 346 { 347 testNoAlloc({} ()); 348 349 testThrown!(TestException)( 350 testNoAlloc({ auto x = new int; } ()) 351 ); 352 } 353 354 unittest 355 { 356 auto t = new NamedTest("struct"); 357 358 struct S { int a; char[2] arr; } 359 360 try 361 { 362 t.test!("==")(S(1, ['a', 'b']), S(2, ['c', 'd'])); 363 assert(false); 364 } 365 catch (TestException e) 366 { 367 assert(e.message() == `[struct] expression '{ a: 1, arr: "ab" } == { a: 2, arr: "cd" }' evaluates to false`); 368 } 369 } 370 371 unittest 372 { 373 auto t = new NamedTest("typedef"); 374 375 mixin(Typedef!(int, "MyInt")); 376 377 try 378 { 379 t.test!("==")(cast(MyInt)10, cast(MyInt)20); 380 assert(false); 381 } 382 catch (TestException e) 383 { 384 assert(e.message() == `[typedef] expression '10 == 20' evaluates to false`); 385 } 386 }