1 /// When things go wrong.... 2 module mecca.lib.exception; 3 4 // Licensed under the Boost license. Full copyright information in the AUTHORS file 5 6 public import core.exception: AssertError, RangeError; 7 public import std.exception: ErrnoException; 8 import std.algorithm : min, copy; 9 import std.traits; 10 import core.exception: assertHandler; 11 import core.runtime: Runtime, defaultTraceHandler; 12 13 import mecca.log; 14 import mecca.lib.reflection: as; 15 import mecca.lib..string : nogcFormat, nogcRtFormat; 16 17 // Disable tracing instrumentation for the whole file 18 @("notrace") void traceDisableCompileTimeInstrumentation(); 19 20 /// Returns `true` if an assert was thrown 21 /// 22 /// Will not be set to `true` under unittest builds, where an `AssertError` is thrown instead of fairly immediately 23 /// quitting 24 @property bool assertRaised() nothrow @safe @nogc { 25 return _assertInProgress; 26 } 27 28 private bool _assertInProgress; 29 30 // The default TraceInfo provided by DMD is not `@nogc` 31 private extern(C) nothrow @nogc { 32 int backtrace(void** buffer, int size); 33 34 static if (__VERSION__ < 2077) { 35 pragma(mangle, "_D4core7runtime19defaultTraceHandlerFPvZ16DefaultTraceInfo6__ctorMFZC4core7runtime19defaultTraceHandlerFPvZ16DefaultTraceInfo") 36 void defaultTraceInfoCtor(Object); 37 } else static if (__VERSION__ < 2088) { 38 pragma(mangle, "_D4core7runtime19defaultTraceHandlerFPvZ16DefaultTraceInfo6__ctorMFZCQCpQCnQCiFQBqZQBr") 39 void defaultTraceInfoCtor(Object); 40 } else { 41 pragma(mangle, "_D4core7runtime16DefaultTraceInfo6__ctorMFZCQBqQBoQBj") 42 void defaultTraceInfoCtor(Object); 43 } 44 } 45 private __gshared static TypeInfo_Class defaultTraceTypeInfo; 46 47 shared static this() { 48 defaultTraceTypeInfo = typeid(cast(Object)defaultTraceHandler(null)); 49 assert(defaultTraceTypeInfo.name == "core.runtime.defaultTraceHandler.DefaultTraceInfo", defaultTraceTypeInfo.name); 50 assert(defaultTraceTypeInfo.initializer.length <= ExcBuf.MAX_TRACEBACK_SIZE); 51 assert(DefaultTraceInfoABI.sizeof <= defaultTraceTypeInfo.initializer.length); 52 version (unittest) {} else { 53 assertHandler = &assertHandlerImpl; 54 } 55 } 56 57 extern(C) private void ALREADY_EXTRACTING_STACK() { 58 assert (false, "you're not supposed to call this function"); 59 } 60 61 /** 62 * Extract the stack backtrace. 63 * 64 * The pointers returned from the function are one less than the actual number. This is so that a symbol lookup will report the 65 * correct function even when the call to _d_throw_exception was the last thing in it. 66 * 67 * Params: 68 * callstack = range to receive the call stack pointers 69 * skip = number of near frames to skip. 70 * 71 * Retruns: 72 * Range where the actual pointers reside. 73 */ 74 void*[] extractStack(void*[] callstack, size_t skip = 0) nothrow @trusted @nogc { 75 /* thread local */ static bool alreadyExtractingStack = false; 76 if (alreadyExtractingStack) { 77 // stack extraction is apparently non re-entrant, and because we want signal handlers 78 // to be able to use this function, we just return a mock stack in this case. 79 callstack[0] = &ALREADY_EXTRACTING_STACK; 80 callstack[1] = &ALREADY_EXTRACTING_STACK; 81 callstack[2] = &ALREADY_EXTRACTING_STACK; 82 callstack[3] = null; 83 return callstack[0 .. 4]; 84 } 85 alreadyExtractingStack = true; 86 scope(exit) alreadyExtractingStack = false; 87 88 auto numFrames = backtrace(callstack.ptr, cast(int)callstack.length); 89 auto res = callstack[skip .. numFrames]; 90 91 // Adjust the locations by one byte so they point inside the function (as 92 // required by backtrace_symbols) even if the call to _d_throw_exception 93 // was the very last instruction in the function. 94 foreach (ref c; res) { 95 c -= 1; 96 } 97 return res; 98 } 99 100 struct DefaultTraceInfoABI { 101 import mecca.lib.reflection: isVersion; 102 103 void* _vtbl; 104 void* _monitor; 105 void* _interface; // introduced in DMD 2.071 106 // make sure the ABI matches 107 static assert ({static interface I {} static class C: I {} return __traits(classInstanceSize, C);}() == (void*[3]).sizeof); 108 109 int numframes; 110 void*[0] callstack; 111 112 static DefaultTraceInfoABI* extract(Throwable.TraceInfo traceInfo) nothrow @trusted @nogc { 113 auto obj = cast(Object)traceInfo; 114 assert (typeid(obj).name == "core.runtime.defaultTraceHandler.DefaultTraceInfo", typeid(obj).name); 115 return cast(DefaultTraceInfoABI*)(cast(void*)obj); 116 } 117 static DefaultTraceInfoABI* extract(Throwable ex) nothrow @safe @nogc { 118 return extract(ex.info); 119 } 120 @property void*[] frames() return nothrow @trusted @nogc { 121 return callstack.ptr[0 .. numframes]; 122 } 123 } 124 125 /// Static buffer for storing no GC exceptions 126 struct ExcBuf { 127 enum MAX_EXCEPTION_INSTANCE_SIZE = 256; 128 enum MAX_EXCEPTION_MESSAGE_SIZE = 256; 129 enum MAX_TRACEBACK_SIZE = 1064; 130 131 ubyte[MAX_EXCEPTION_INSTANCE_SIZE] ex; 132 ubyte[MAX_TRACEBACK_SIZE] ti; 133 char[MAX_EXCEPTION_MESSAGE_SIZE] msgBuf; 134 135 /// Get the Throwable stored in the buffer 136 Throwable get() return nothrow @trusted @nogc { 137 if (*(cast(void**)ex.ptr) is null) { 138 return null; 139 } 140 return cast(Throwable)ex.ptr; 141 } 142 143 /// Set the exception that buffer is to hold. 144 Throwable set(Throwable t, bool setTraceback = false) nothrow @trusted @nogc { 145 static assert (this.ex.offsetof == 0); 146 if (t is null) { 147 *(cast(void**)ex.ptr) = null; 148 return null; 149 } 150 151 if (t is get()) { 152 // don't assign to yourself to yourself 153 if (setTraceback) { 154 this.setTraceback(t); 155 } 156 return t; 157 } 158 159 auto len = typeid(t).initializer.length; 160 ex[0 .. len] = (cast(ubyte*)t)[0 .. len]; 161 auto tobj = cast(Throwable)ex.ptr; 162 163 if (setTraceback) { 164 this.setTraceback(t); 165 } 166 else { 167 if (t.info is null) { 168 tobj.info = null; 169 *(cast(void**)ti.ptr) = null; 170 } 171 else { 172 auto tinfo = cast(Object)t.info; 173 assert (tinfo !is null, "casting TraceInfo to Object failed"); 174 len = typeid(tinfo).m_init.length; 175 ti[0 .. len] = (cast(ubyte*)tinfo)[0 .. len]; 176 tobj.info = cast(Throwable.TraceInfo)(cast(Object)ti.ptr); 177 } 178 } 179 180 setMsg(t.msg); 181 return tobj; 182 } 183 184 /** 185 * set a Throwable's backtrace to the current point. 186 * 187 * Params: 188 * tobj = the Throwable which backtrace to set. If null, set the buffer's Throwable (which must not be null). 189 */ 190 void setTraceback(Throwable tobj) nothrow @trusted @nogc { 191 if (tobj is null) { 192 tobj = get(); 193 assert (tobj !is null, "setTraceback of unset exception"); 194 } 195 ti[0 .. DefaultTraceInfoABI.sizeof] = cast(ubyte[])(defaultTraceTypeInfo.initializer[0 .. DefaultTraceInfoABI.sizeof]); 196 auto tinfo = cast(Object)ti.ptr; 197 defaultTraceInfoCtor(tinfo); 198 tobj.info = cast(Throwable.TraceInfo)tinfo; 199 } 200 201 /** 202 * Construct a throwable in place 203 * 204 * Params: 205 * file = the reported source file of the exception 206 * line = the reported source file line of the exception 207 * setTraceback = whether to set the stack trace 208 * args = arguments to pass to the exception's constructor 209 */ 210 T construct(T:Throwable, A...)(string file, size_t line, bool setTraceback, auto ref A args) nothrow @trusted @nogc 211 { 212 T t = constructHelper!T(file, line, setTraceback, args); 213 setMsg(t.msg, t); 214 return t; 215 } 216 217 /** 218 * Construct a Throwable, formatting the message 219 * 220 * Construct a `Throwable` that has a constructor that accepts a single string argument (such as `Exception`). Allows 221 * formatting arguments into the string in a non GC way. 222 * 223 * Second form of the function receives the string as a template argument, and verifies that the arguments match the 224 * format string. 225 */ 226 T constructFmt(T: Throwable = Exception, A...)(string file, size_t line, string fmt, auto ref A args) @trusted { 227 auto tmpMsg = nogcRtFormat(msgBuf[], fmt, args); 228 return constructHelper!T(file, line, true, tmpMsg); 229 } 230 231 unittest { 232 ExcBuf ex; 233 ex.constructFmt!Exception("file.d", 31337, "%s was a %s %s", "pappa", "rolling", "stoner"); 234 235 assert( ex.get().msg=="pappa was a rolling stoner" ); 236 } 237 238 /// ditto 239 T constructFmt(string fmt, T: Throwable = Exception, A...)(string file, size_t line, auto ref A args) @trusted { 240 auto tmpMsg = nogcFormat!fmt(msgBuf[], args); 241 return constructHelper!T(file, line, true, tmpMsg); 242 } 243 244 unittest { 245 ExcBuf ex; 246 ex.constructFmt!("I'm %s in %s", Exception)("file.d", 31337, "an Englishman", "New York"); 247 248 assert( ex.get().msg=="I'm an Englishman in New York" ); 249 250 static assert( !__traits(compiles, 251 ex.constructFmt!("No arguments, please", Exception)("file.d", 31337, "An argument")) ); 252 } 253 254 void setMsg(const(char)[] msg2, Throwable tobj = null) nothrow @nogc { 255 if (tobj is null) { 256 tobj = get(); 257 assert (tobj); 258 } 259 if (msg2 is null || msg2.length == 0) { 260 tobj.msg = null; 261 } 262 else if (msg2.length > msgBuf.length) { 263 msgBuf[] = msg2[0 .. msgBuf.length]; 264 tobj.msg = cast(string)msgBuf[]; 265 } 266 else { 267 // msgBuf[0 .. msg2.length] = msg2[]; 268 // The buffer we copy from and to are, occasionally, the same buffer. The above line is illegal in that case 269 copy(msg2, msgBuf[0..msg2.length]); 270 tobj.msg = cast(string)msgBuf[0 .. msg2.length]; 271 } 272 } 273 274 static bool isGCException(Throwable ex) { 275 import core.memory: GC; 276 return GC.addrOf(cast(void*)ex) !is null; 277 } 278 279 static Throwable toGC(Throwable ex, bool forceCopy=false) { 280 if (!forceCopy && isGCException(ex)) { 281 // already GC-allocated 282 return ex; 283 } 284 auto buf = new ExcBuf; 285 return buf.set(ex); 286 } 287 288 private: 289 T constructHelper(T:Throwable, A...)( 290 string file, size_t line, bool setTraceback, auto ref A args) nothrow @trusted @nogc 291 { 292 static assert (__traits(classInstanceSize, T) <= ExcBuf.MAX_EXCEPTION_INSTANCE_SIZE); 293 294 // create the exception 295 ex[0 .. __traits(classInstanceSize, T)] = cast(ubyte[])typeid(T).initializer[]; 296 auto t = cast(T)ex.ptr; 297 as!"nothrow @nogc"({t.__ctor(args);}); 298 t.file = file; 299 t.line = line; 300 t.info = null; 301 302 if (setTraceback) { 303 this.setTraceback(t); 304 } 305 306 return t; 307 } 308 } 309 310 /* thread local*/ static ExcBuf _tlsExcBuf; 311 /* thread local*/ static ExcBuf* _currExcBuf; 312 /* thread local*/ static this() {_currExcBuf = &_tlsExcBuf;} 313 314 void switchCurrExcBuf(ExcBuf* newCurrentExcBuf) nothrow @safe @nogc { 315 if (newCurrentExcBuf !is null) 316 _currExcBuf = newCurrentExcBuf; 317 else 318 _currExcBuf = &_tlsExcBuf; 319 } 320 321 T mkEx(T: Throwable, string file = __FILE_FULL_PATH__, size_t line = __LINE__, A...)(auto ref A args) @safe @nogc { 322 version(LDC) 323 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 324 pragma(inline, true); 325 326 return mkExLine!T(file, line, args); 327 } 328 329 T mkExLine(T: Throwable, A...)(string file, size_t line, auto ref A args) @trusted @nogc { 330 static assert(!isNested!T, "Cannot use mkEx to create exception of type " ~ T.stringof ~ " that needs a context pointer"); 331 return _currExcBuf.construct!T(file, line, true, args); 332 } 333 334 T mkExFmt(T: Throwable, string file = __FILE_FULL_PATH__, size_t line = __LINE__, A...)(string fmt, auto ref A args) @safe @nogc 335 { 336 version(LDC) 337 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 338 pragma(inline, true); 339 340 return _currExcBuf.constructFmt!T(file, line, fmt, args); 341 } 342 343 T mkExFmt(string fmt, T: Throwable = Exception, string file = __FILE_FULL_PATH__, size_t line = __LINE__, A...)(auto ref A args) 344 @safe @nogc 345 { 346 version(LDC) 347 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 348 pragma(inline, true); 349 350 return _currExcBuf.constructFmt!(fmt, T)(file, line, args); 351 } 352 353 Throwable setEx(Throwable ex, bool setTraceback = false) nothrow @safe @nogc { 354 return _currExcBuf.set(ex, setTraceback); 355 } 356 357 class RangeErrorWithReason : Error { 358 mixin ExceptionBody; 359 } 360 361 RangeErrorWithReason rangeError(K, string file=__FILE_FULL_PATH__, string mod=__MODULE__, size_t line=__LINE__) 362 (K key, string msg = "Index/key not found") 363 { 364 version(LDC) 365 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 366 pragma(inline, true); 367 368 import std.format : format; 369 static if ( __traits(compiles, format("%s", key))) { 370 return mkExFmt!("%s: %s", RangeErrorWithReason, file, line)(msg, key); 371 } 372 else { 373 return mkExFmt!("%s: %s", RangeErrorWithReason, file, line)(msg, K.stringof); 374 } 375 } 376 377 void enforceFmt(T: Throwable = Exception, string file = __FILE_FULL_PATH__, size_t line = __LINE__, A...)( 378 bool cond, string fmt, auto ref A args) @safe @nogc 379 { 380 version(LDC) 381 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 382 pragma(inline, true); 383 384 if (!cond) { 385 throw mkExFmt!(T, file, line)(fmt, args); 386 } 387 } 388 389 390 mixin template ExceptionBody(string msg) { 391 this(string file = __FILE_FULL_PATH__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow @nogc { 392 super(msg, file, line, next); 393 } 394 } 395 396 mixin template ExceptionBody() { 397 this(string msg, string file = __FILE_FULL_PATH__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow @nogc { 398 super(msg, file, line, next); 399 } 400 /+import mecca.lib.exception: ExcBuf; 401 __gshared static ExcBuf* _singletonExBuf; 402 403 shared static this() { 404 _singletonExBuf = new ExcBuf; 405 } 406 @("notrace") static typeof(this) singleton(string file = __FILE_FULL_PATH__, size_t line = __LINE__) { 407 import mecca.tracing.api: LOG_TRACEBACK; 408 auto ex = _singletonExBuf.construct!(typeof(this))(file, line, true, (staticMsg.length == 0 ? typeof(this).stringof : staticMsg)); 409 LOG_TRACEBACK(ex); 410 return ex; 411 }+/ 412 } 413 414 unittest { 415 import std.stdio; 416 static class MyException: Exception {mixin ExceptionBody;} 417 static class YourException: Exception {mixin ExceptionBody;} 418 419 bool thrown1 = false; 420 try { 421 throw mkEx!MyException("hello world"); 422 } 423 catch (MyException ex) { 424 assert (ex.msg == "hello world"); 425 thrown1 = true; 426 } 427 assert (thrown1); 428 429 auto ex2 = new YourException("foo bar"); 430 bool thrown2 = false; 431 try { 432 throw setEx(ex2); 433 } 434 catch (YourException ex) { 435 assert (ex !is ex2); 436 assert(ex.msg == "foo bar"); 437 thrown2 = true; 438 } 439 assert (thrown2); 440 } 441 442 private @notrace void assertHandlerImpl(string file, size_t line, string msg) nothrow @nogc { 443 pragma(inline, true); 444 DIE(msg, file, line); 445 } 446 447 void function(string msg, string file, size_t line) blowUpHandler; 448 449 @notrace void DIE(string msg, string file = __FILE_FULL_PATH__, size_t line = __LINE__, bool doAbort=false) nothrow @nogc { 450 import core.sys.posix.unistd: write, _exit; 451 import core.stdc.stdlib: abort; 452 import core.atomic: cas; 453 454 // block threads racing into this function 455 shared static bool recLock = false; 456 while (!cas(&recLock, false, true)) {} 457 458 __gshared static ExcBuf excBuf; 459 as!"nothrow @nogc"({ 460 if( loggingInitialized ) { 461 META!"Assertion failure(%s:%s) %s"(file, line, msg); 462 flushLog(); 463 } 464 auto ex = excBuf.construct!AssertError(file, line, true, msg); 465 version(unittest) { 466 recLock = false; 467 throw ex; 468 } else { 469 _assertInProgress = true; 470 ex.toString((text){write(2, text.ptr, text.length);}); 471 if (doAbort) { 472 abort(); 473 } 474 else { 475 _exit(1); 476 } 477 } 478 }); 479 assert(false); 480 } 481 482 unittest { 483 assertThrows!AssertError( DIE("Test UT DIE behavior") ); 484 } 485 486 void ABORT(string msg, string file = __FILE_FULL_PATH__, size_t line = __LINE__) nothrow @nogc { 487 DIE(msg, file, line, true); 488 } 489 490 @notrace void ASSERT 491 (string fmt, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__, T...) 492 (bool cond, scope lazy T args) 493 pure nothrow @trusted @nogc 494 { 495 version(LDC) 496 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 497 pragma(inline, true); 498 499 if (cond) { 500 return; 501 } 502 503 scope f = () { 504 META!("ASSERT: " ~ fmt, file, mod, line)(args); 505 static if( !LogToConsole ) { 506 // Also log to stderr, as the logger doesn't do that for us. 507 import std.stdio: stderr; 508 stderr.writefln( "Assertion failure at %s:%s: " ~ fmt, file, line, args ); 509 } 510 }; 511 as!"@nogc pure nothrow"(f); 512 version(unittest ){ 513 as!"@nogc pure nothrow"({ 514 import std..string: format; 515 throw new AssertError( format("Assertion failure: " ~ fmt, args), file, line); 516 }); 517 } 518 else { 519 as!"pure"({ 520 _assertInProgress = true; 521 dumpStackTrace(); 522 DIE("Assertion failed", file, line); 523 }); 524 } 525 } 526 527 void enforceNGC(Ex : Throwable = Exception, string file = __FILE_FULL_PATH__, size_t line = __LINE__) 528 (bool value, scope lazy string msg = null) @trusted @nogc 529 { 530 version(LDC) 531 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 532 pragma(inline, true); 533 534 if( !value ) { 535 string evaluatedMsg; 536 as!"@nogc"({evaluatedMsg = msg;}); 537 throw mkEx!Ex(evaluatedMsg, file, line); 538 } 539 } 540 541 void errnoEnforceNGC(string file = __FILE_FULL_PATH__, size_t line = __LINE__) 542 (bool value, scope lazy string msg = null) @trusted @nogc 543 { 544 version(LDC) 545 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 546 pragma(inline, true); 547 548 as!"@nogc"({ enforceNGC!(ErrnoException, file, line)(value, msg); }); 549 } 550 551 version(assert) { 552 alias DBG_ASSERT = ASSERT; 553 } 554 else { 555 void DBG_ASSERT(string fmt, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__, T...) 556 (scope lazy bool cond, scope lazy T args) @nogc 557 { 558 pragma(inline, true); 559 } 560 } 561 562 unittest { 563 ASSERT!"oh no: %s"(true, "foobar"); 564 } 565 566 567 /** Assert on a generic operation 568 * 569 * This is a generic assert based on an operation between two arguments. This function's advantage over merely asserting 570 * with the operation is that, in case of assert failure, both values compared are printed in addition to the assert 571 * message. 572 * 573 * If asserts are disabled, this function compiles away to nothing. 574 */ 575 @notrace void assertOp(string op, L, R, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__) 576 (L lhs, R rhs, scope lazy string msg="") nothrow 577 { 578 version(assert) { 579 version(LDC) 580 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 581 pragma(inline, true); 582 583 static assert( 584 !is(Unqual!LHS == enum) || is(Unqual!LHS == Unqual!RHS), 585 "comparing different enums is unsafe: " ~ LHS.stringof ~ " != " ~ RHS.stringof); 586 587 import std.meta: staticIndexOf; 588 enum idx = staticIndexOf!(op, "==", "!=", ">", "<", ">=", "<=", "in", "!in", "is", "!is"); 589 static assert (idx >= 0, "assertOp called with operation \"" ~ op ~ "\" which is not supported"); 590 enum inverseOp = ["!=", "==", "<=", ">=", "<", ">", "!in", "in", "!is", "is"][idx]; 591 592 auto lhsVal = lhs; 593 auto rhsVal = rhs; 594 595 if( mixin("lhsVal " ~ op ~ " rhsVal") ) 596 return; 597 598 void safefify() pure @nogc @trusted { 599 as!"@nogc pure nothrow"({ 600 import std.format; 601 DIE(format("Assert: %s %s %s %s", lhs, inverseOp, rhs, msg), file, line); 602 }); 603 } 604 605 safefify(); 606 } 607 } 608 609 /// Assert that two values are equal. 610 @notrace void assertEQ(L, R, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__)( 611 L lhs, R rhs, scope lazy string msg="") nothrow @trusted 612 { 613 assertOp!("==", L, R, file, mod, line)(lhs, rhs, as!"@nogc nothrow"( {return msg;} )); 614 } 615 /// Assert that two values are not equal. 616 @notrace void assertNE(L, R, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__)( 617 L lhs, R rhs, scope lazy string msg="") nothrow @trusted 618 { 619 assertOp!("!=", L, R, file, mod, line)(lhs, rhs, as!"@nogc nothrow"( {return msg;} )); 620 } 621 /// Assert that `lhs` is greater than `rhs`. 622 @notrace void assertGT(L, R, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__)( 623 L lhs, R rhs, scope lazy string msg="") nothrow @trusted 624 { 625 assertOp!(">", L, R, file, mod, line)(lhs, rhs, as!"@nogc nothrow"( {return msg;} )); 626 } 627 /// Assert that `lhs` is greater or equals to `rhs`. 628 @notrace void assertGE(L, R, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__)( 629 L lhs, R rhs, scope lazy string msg="") nothrow @trusted 630 { 631 assertOp!(">=", L, R, file, mod, line)(lhs, rhs, as!"@nogc nothrow"( {return msg;} )); 632 } 633 /// Assert that `lhs` is lesser than `rhs`. 634 @notrace void assertLT(L, R, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__)( 635 L lhs, R rhs, scope lazy string msg="") nothrow @trusted 636 { 637 assertOp!("<", L, R, file, mod, line)(lhs, rhs, as!"@nogc nothrow"( {return msg;} )); 638 } 639 /// Assert that `lhs` is lesser or equal to `rhs`. 640 @notrace void assertLE(L, R, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__)( 641 L lhs, R rhs, scope lazy string msg="") nothrow @trusted 642 { 643 assertOp!("<=", L, R, file, mod, line)(lhs, rhs, as!"@nogc nothrow"( {return msg;} )); 644 } 645 646 version(unittest) { 647 void assertThrows(T = Throwable, E, string file = __FILE_FULL_PATH__, string mod = __MODULE__, size_t line = __LINE__) 648 (scope lazy E expr) 649 { 650 try { 651 expr(); 652 } 653 catch (Throwable ex) { 654 ASSERT!("Threw %s instead of %s", file, mod, line)(cast(T)ex !is null, typeid(ex).name, T.stringof); 655 return; 656 } 657 ASSERT!("Did not throw", file, mod, line)(false); 658 } 659 660 unittest { 661 static bool thrower(T : Throwable)(string msg) { 662 throw new T(msg); 663 } 664 665 try { 666 assertThrows!AssertError( 12 ); 667 assert(false, "assertThrows did not detect code did not throw"); 668 } catch(AssertError ex) { 669 } 670 671 try { 672 assertThrows!AssertError( thrower!ErrnoException("Nothing wrong") ); 673 assert(false, "assertThrows did not detect code threw the wrong exception"); 674 } catch(AssertError ex) { 675 } 676 } 677 } 678 679 unittest { 680 assertEQ(7, 7); 681 assertNE(7, 17); 682 assertThrows!AssertError(assertEQ(7, 17)); 683 } 684 685 int errnoCall(alias F, string file=__FILE_FULL_PATH__, size_t line=__LINE__)(Parameters!F args) @nogc if (is(ReturnType!F == int)) 686 { 687 version(LDC) 688 // Must inline because of __FILE_FULL_PATH__ as template parameter. https://github.com/ldc-developers/ldc/issues/1703 689 pragma(inline, true); 690 691 int res = F(args); 692 if (res < 0) { 693 import std.range: repeat; 694 import std..string; 695 enum fmt = __traits(identifier, F) ~ "(" ~ "%s".repeat(args.length).join(", ") ~ ")"; 696 throw mkExFmt!(fmt, ErrnoException, file, line)(args); 697 } 698 return res; 699 } 700 701 unittest { 702 import core.sys.posix.unistd: dup, close; 703 auto newFd = errnoCall!dup(1); 704 assert (newFd >= 0); 705 errnoCall!close(newFd); 706 // double close will throw 707 assertThrows!ErrnoException(errnoCall!close(newFd)); 708 }