1 /++ 2 A module containing custom exceptions for display convenience 3 4 Copyright: © 2017 Szabo Bogdan 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Szabo Bogdan 7 +/ 8 module trial.stackresult; 9 10 import std.conv; 11 import std.regex; 12 import std.exception; 13 import std.stdio; 14 import std..string; 15 import std.algorithm; 16 17 import core.demangle; 18 19 version (Have_fluent_asserts) { } else { 20 auto toTestException(Throwable t) 21 { 22 return t; 23 } 24 } 25 26 version (Have_fluent_asserts) { 27 28 import fluentasserts.core.base; 29 import fluentasserts.core.results; 30 31 /// 32 class TestExceptionWrapper : TestException { 33 private { 34 TestException exception; 35 IResult[] results; 36 } 37 38 /// 39 this(TestException exception, IResult[] results, string fileName, size_t line, Throwable next = null) { 40 this.exception = exception; 41 this.results = results; 42 43 super(results, fileName, line, next); 44 45 this.msg = exception.msg ~ "\n" ~ this.msg; 46 } 47 48 /// 49 override void print(ResultPrinter printer) { 50 exception.print(printer); 51 52 results.each!(a => a.print(printer)); 53 } 54 55 /// 56 override string toString() { 57 return exception.toString ~ results.map!(a => a.toString).join("\n").to!string; 58 } 59 } 60 61 /// The message of a wrapped exception should contain the original exception 62 unittest { 63 auto exception = new TestException([ new MessageResult("first message") ], "", 0); 64 auto wrappedException = new TestExceptionWrapper(exception, [ new MessageResult("second message") ], "", 0); 65 66 wrappedException.msg.should.equal("first message\n\nsecond message\n"); 67 } 68 69 /// Converts a Throwable to a TestException which improves the failure verbosity 70 TestException toTestException(Throwable t) 71 { 72 auto exception = cast(TestException) t; 73 74 if (exception is null) 75 { 76 IResult[] results = [cast(IResult) new MessageResult(t.classinfo.name ~ ": " ~ t.msg)]; 77 78 if (t.file.indexOf("../") == -1) 79 { 80 results ~= cast(IResult) new SourceResult(t.file, t.line); 81 } 82 83 if (t !is null && t.info !is null) 84 { 85 results ~= cast(IResult) new StackResult(t.info); 86 } 87 88 exception = new TestException(results, t.file, t.line, t); 89 } else { 90 exception = new TestExceptionWrapper(exception, [ cast(IResult) new StackResult(t.info) ], t.file, t.line, t); 91 } 92 93 return exception; 94 } 95 96 @("toTestException should convert an Exception from the current project to a TestException with 2 reporters") 97 unittest 98 { 99 auto exception = new Exception("random text"); 100 auto testException = exception.toTestException; 101 102 (testException !is null).should.equal(true); 103 testException.toString.should.contain("random text"); 104 testException.toString.should.contain("lifecycle/trial/runner.d"); 105 } 106 107 @("toTestException should convert an Exception from other project to a TestException with 1 reporter") 108 unittest 109 { 110 auto exception = new Exception("random text", "../file.d"); 111 auto testException = exception.toTestException; 112 113 (testException !is null).should.equal(true); 114 testException.toString.should.contain("random text"); 115 testException.toString.should.not.contain("lifecycle/trial/runner.d"); 116 } 117 118 /// A structure that allows you to detect which modules are relevant to display 119 struct ExternalValidator 120 { 121 122 /// The list of external modules like the standard library or dub dependencies 123 string[] externalModules; 124 125 /// Check if the provided name comes from an external dependency 126 bool isExternal(const string name) @safe 127 { 128 auto reversed = name.dup; 129 reverse(reversed); 130 131 string substring = name; 132 int sum = 0; 133 int index = 0; 134 foreach (ch; reversed) 135 { 136 if (ch == ')') 137 { 138 sum++; 139 } 140 141 if (ch == '(') 142 { 143 sum--; 144 } 145 146 if (sum == 0) 147 { 148 break; 149 } 150 index++; 151 } 152 153 auto tmpSubstring = reversed[index .. $]; 154 reverse(tmpSubstring); 155 substring = tmpSubstring.to!string; 156 157 auto wordEnd = substring.lastIndexOf(' ') + 1; 158 auto chainEnd = substring.lastIndexOf(").") + 1; 159 160 if (chainEnd > wordEnd) 161 { 162 return isExternal(name[0 .. chainEnd]); 163 } 164 165 auto functionName = substring[wordEnd .. $]; 166 167 return !externalModules.filter!(a => functionName.indexOf(a) == 0).empty; 168 } 169 } 170 171 @("It should detect external functions") 172 unittest 173 { 174 auto validator = ExternalValidator(["selenium.api", "selenium.session"]); 175 176 validator.isExternal("selenium.api.SeleniumApiConnector selenium.api.SeleniumApiConnector.__ctor()") 177 .should.equal(true); 178 179 validator.isExternal("void selenium.api.SeleniumApiConnector.__ctor()").should.equal(true); 180 181 validator.isExternal( 182 "pure @safe bool selenium.api.enforce!(Exception, bool).enforce(bool, lazy const(char)[], immutable(char)[], ulong)") 183 .should.equal(true); 184 185 validator.isExternal("immutable(immutable(selenium.session.SeleniumSession) function(immutable(char)[], selenium.api.Capabilities, selenium.api.Capabilities, selenium.api.Capabilities)) selenium.session.SeleniumSession.__ctor") 186 .should.equal(true); 187 } 188 189 /// Used to display the stack 190 class StackResult : IResult 191 { 192 static 193 { 194 /// 195 string[] externalModules; 196 } 197 198 /// 199 Frame[] frames; 200 201 /// 202 this(Throwable.TraceInfo t) 203 { 204 foreach (line; t) 205 { 206 auto frame = line.to!string.toFrame; 207 frame.name = demangle(frame.name).to!string; 208 frames ~= frame; 209 } 210 } 211 212 private 213 { 214 auto getFrames() 215 { 216 return frames.until!(a => a.name.indexOf("generated") != -1) 217 .until!(a => a.name.indexOf("D5trial") != -1); 218 } 219 } 220 221 override 222 { 223 /// Converts the result to a string 224 string toString() @safe 225 { 226 string result = "Stack trace:\n-------------------\n...\n"; 227 228 foreach (frame; getFrames) 229 { 230 result ~= frame.toString ~ "\n"; 231 } 232 233 return result ~ "..."; 234 } 235 236 /// Prints the stack using the default writer 237 void print(ResultPrinter printer) 238 { 239 int colorIndex = 0; 240 printer.primary("Stack trace:\n-------------------\n...\n"); 241 242 auto validator = ExternalValidator(externalModules); 243 244 foreach (frame; getFrames) 245 { 246 if (validator.isExternal(frame.name)) 247 { 248 printer.primary(frame.toString); 249 } 250 else 251 { 252 frame.print(printer); 253 } 254 255 printer.primary("\n"); 256 } 257 258 printer.primary("..."); 259 } 260 } 261 } 262 263 @("The stack result should display the stack in a readable form") 264 unittest 265 { 266 Throwable exception; 267 268 try 269 { 270 assert(false, "random message"); 271 } 272 catch (Throwable t) 273 { 274 exception = t; 275 } 276 277 auto result = new StackResult(exception.info).toString; 278 279 result.should.startWith("Stack trace:\n-------------------\n..."); 280 result.should.endWith("\n..."); 281 } 282 } else { 283 284 /// Used to display the stack 285 class StackResult 286 { 287 static 288 { 289 /// 290 string[] externalModules; 291 } 292 293 /// 294 Frame[] frames; 295 296 /// 297 this(Throwable.TraceInfo t) 298 { 299 foreach (line; t) 300 { 301 auto frame = line.to!string.toFrame; 302 frame.name = demangle(frame.name).to!string; 303 frames ~= frame; 304 } 305 } 306 307 private 308 { 309 auto getFrames() 310 { 311 return frames.until!(a => a.name.indexOf("generated") != -1) 312 .until!(a => a.name.indexOf("D5trial") != -1); 313 } 314 } 315 316 override 317 { 318 /// Converts the result to a string 319 string toString() @safe 320 { 321 string result = "Stack trace:\n-------------------\n...\n"; 322 323 foreach (frame; getFrames) 324 { 325 result ~= frame.toString ~ "\n"; 326 } 327 328 return result ~ "..."; 329 } 330 } 331 } 332 } 333 334 /// Represents a stack frame 335 struct Frame 336 { 337 /// 338 int index = -1; 339 340 /// 341 string moduleName; 342 343 /// 344 string address; 345 346 /// 347 string name; 348 349 /// 350 string offset; 351 352 /// 353 string file; 354 355 /// 356 int line = -1; 357 358 /// 359 bool invalid = true; 360 361 /// 362 string raw; 363 364 string toString() const @safe { 365 if(raw != "") { 366 return raw; 367 } 368 369 string result; 370 371 if(index >= 0) { 372 result ~= leftJustifier(index.to!string, 4).to!string; 373 } 374 375 result ~= address ~ " "; 376 result ~= name == "" ? "????" : name; 377 378 if(moduleName != "") { 379 result ~= " at " ~ moduleName; 380 } 381 382 if(offset != "") { 383 result ~= " + " ~ offset; 384 } 385 386 if(file != "") { 387 result ~= " (" ~ file; 388 389 if(line > 0) { 390 result ~= ":" ~ line.to!string; 391 } 392 393 result ~= ")"; 394 } 395 396 return result; 397 } 398 399 version(Have_fluent_asserts) { 400 void print(ResultPrinter printer) @safe 401 { 402 if(raw != "") { 403 printer.primary(raw); 404 405 return; 406 } 407 408 if(index >= 0) { 409 printer.info(leftJustifier(index.to!string, 4).to!string); 410 } 411 412 printer.primary(address ~ " "); 413 printer.info(name == "" ? "????" : name); 414 415 if(moduleName != "") { 416 printer.primary(" at "); 417 printer.info(moduleName); 418 } 419 420 if(offset != "") { 421 printer.primary(" + "); 422 printer.info(offset); 423 } 424 425 if(file != "") { 426 printer.primary(" ("); 427 printer.info(file); 428 429 if(line > 0) { 430 printer.primary(":"); 431 printer.info(line.to!string); 432 } 433 434 printer.primary(")"); 435 } 436 } 437 } 438 } 439 440 /// The frame should convert a frame to string 441 unittest 442 { 443 Frame(10, "some.module", "0xffffff", "name", "offset", "file.d", 120).toString.should.equal( 444 `10 0xffffff name at some.module + offset (file.d:120)` 445 ); 446 } 447 448 /// The frame should not output an index < 0 or a line < 0 449 unittest 450 { 451 Frame(-1, "some.module", "0xffffff", "name", "offset", "file.d", -1).toString.should.equal( 452 `0xffffff name at some.module + offset (file.d)` 453 ); 454 } 455 456 /// The frame should not output the file if it is missing from the stack 457 unittest 458 { 459 Frame(-1, "some.module", "0xffffff", "name", "offset", "", 10).toString.should.equal( 460 `0xffffff name at some.module + offset` 461 ); 462 } 463 464 /// The frame should not output the module if it is missing from the stack 465 unittest 466 { 467 Frame(-1, "", "0xffffff", "name", "offset", "", 10).toString.should.equal( 468 `0xffffff name + offset` 469 ); 470 } 471 472 /// The frame should not output the offset if it is missing from the stack 473 unittest 474 { 475 Frame(-1, "", "0xffffff", "name", "", "", 10).toString.should.equal( 476 `0xffffff name` 477 ); 478 } 479 480 /// The frame should display `????` when the name is missing 481 unittest 482 { 483 Frame(-1, "", "0xffffff", "", "", "", 10).toString.should.equal( 484 `0xffffff ????` 485 ); 486 } 487 488 version(unittest) { 489 version(Have_fluent_asserts): 490 class MockPrinter : ResultPrinter { 491 string buffer; 492 493 void primary(string val) { 494 buffer ~= val; 495 } 496 497 void info(string val) { 498 buffer ~= "[info:" ~ val ~ "]"; 499 } 500 501 void danger(string val) { 502 buffer ~= "[danger:" ~ val ~ "]"; 503 } 504 505 void success(string val) { 506 buffer ~= "[success:" ~ val ~ "]"; 507 } 508 509 void dangerReverse(string val) { 510 buffer ~= "[dangerReverse:" ~ val ~ "]"; 511 } 512 513 void successReverse(string val) { 514 buffer ~= "[successReverse:" ~ val ~ "]"; 515 } 516 } 517 } 518 519 /// The frame should print all fields 520 unittest 521 { 522 auto printer = new MockPrinter; 523 Frame(10, "some.module", "0xffffff", "name", "offset", "file.d", 120).print(printer); 524 525 printer.buffer.should.equal( 526 `[info:10 ]0xffffff [info:name] at [info:some.module] + [info:offset] ([info:file.d]:[info:120])` 527 ); 528 } 529 530 /// The frame should not print an index < 0 or a line < 0 531 unittest 532 { 533 auto printer = new MockPrinter; 534 Frame(-1, "some.module", "0xffffff", "name", "offset", "file.d", -1).print(printer); 535 536 printer.buffer.should.equal( 537 `0xffffff [info:name] at [info:some.module] + [info:offset] ([info:file.d])` 538 ); 539 } 540 541 /// The frame should not print the file if it's missing 542 unittest 543 { 544 auto printer = new MockPrinter; 545 Frame(-1, "some.module", "0xffffff", "name", "offset", "", 10).print(printer); 546 547 printer.buffer.should.equal( 548 `0xffffff [info:name] at [info:some.module] + [info:offset]` 549 ); 550 } 551 552 /// The frame should not print the module if it's missing 553 unittest 554 { 555 auto printer = new MockPrinter; 556 Frame(-1, "", "0xffffff", "name", "offset", "", 10).print(printer); 557 558 printer.buffer.should.equal( 559 `0xffffff [info:name] + [info:offset]` 560 ); 561 } 562 563 /// The frame should not print the offset if it's missing 564 unittest 565 { 566 auto printer = new MockPrinter; 567 Frame(-1, "", "0xffffff", "name", "", "", 10).print(printer); 568 569 printer.buffer.should.equal( 570 `0xffffff [info:name]` 571 ); 572 } 573 574 /// The frame should print ???? when the name is missing 575 unittest 576 { 577 auto printer = new MockPrinter; 578 Frame(-1, "", "0xffffff", "", "", "", 10).print(printer); 579 580 printer.buffer.should.equal( 581 `0xffffff [info:????]` 582 ); 583 } 584 585 immutable static 586 { 587 string index = `(?P<index>[0-9]+)`; 588 string moduleName = `(?P<module>\S+)`; 589 string address = `(?P<address>0x[0-9a-fA-F]+)`; 590 string name = `(?P<name>.+)`; 591 string offset = `(?P<offset>(0x[0-9A-Za-z]+)|([0-9]+))`; 592 string file = `(?P<file>.+)`; 593 string linePattern = `(?P<line>[0-9]+)`; 594 } 595 596 /// Parse a MacOS string frame 597 Frame toDarwinFrame(string line) 598 { 599 Frame frame; 600 601 auto darwinPattern = index ~ `(\s+)` ~ moduleName ~ `(\s+)` ~ address ~ `(\s+)` 602 ~ name ~ `\s\+\s` ~ offset; 603 604 auto matched = matchFirst(line, darwinPattern); 605 606 if(matched.length < 5) { 607 return frame; 608 } 609 610 frame.invalid = false; 611 frame.index = matched["index"].to!int; 612 frame.moduleName = matched["module"]; 613 frame.address = matched["address"]; 614 frame.name = matched["name"]; 615 frame.offset = matched["offset"]; 616 617 return frame; 618 } 619 620 /// Parse a Windows string frame 621 Frame toWindows1Frame(string line) 622 { 623 Frame frame; 624 625 auto matched = matchFirst(line, 626 address ~ `(\s+)in(\s+)` ~ name ~ `(\s+)at(\s+)` ~ file ~ `\(` ~ linePattern ~ `\)`); // ~ ); 627 628 if(matched.length < 4) { 629 return frame; 630 } 631 632 frame.address = matched["address"]; 633 frame.name = matched["name"]; 634 frame.file = matched["file"]; 635 frame.line = matched["line"].to!int; 636 637 frame.invalid = frame.address == "" || frame.name == "" || frame.file == ""; 638 639 return frame; 640 } 641 642 /// ditto 643 Frame toWindows2Frame(string line) 644 { 645 Frame frame; 646 647 auto matched = matchFirst(line, address ~ `(\s+)in(\s+)` ~ name); 648 649 if(matched.length < 2) { 650 return frame; 651 } 652 653 frame.address = matched["address"]; 654 frame.name = matched["name"]; 655 656 frame.invalid = frame.address == "" || frame.name == ""; 657 658 return frame; 659 } 660 661 /// Parse a GLibC string frame 662 Frame toGLibCFrame(string line) 663 { 664 Frame frame; 665 666 auto matched = matchFirst(line, moduleName ~ `\(` ~ name ~ `\)\s+\[` ~ address ~ `\]`); 667 668 if(matched.length < 3) { 669 return frame; 670 } 671 672 frame.address = matched["address"]; 673 frame.name = matched["name"]; 674 frame.moduleName = matched["module"]; 675 676 auto plusSign = frame.name.indexOf("+"); 677 678 if (plusSign != -1) 679 { 680 frame.offset = frame.name[plusSign + 1 .. $]; 681 frame.name = frame.name[0 .. plusSign]; 682 } 683 684 frame.invalid = frame.address == "" || frame.name == "" || frame.moduleName == "" || 685 frame.name.indexOf("(") >= 0; 686 687 return frame; 688 } 689 690 /// Parse a NetBsd string frame 691 Frame toNetBsdFrame(string line) 692 { 693 Frame frame; 694 695 auto matched = matchFirst(line, address ~ `\s+<` ~ name ~ `\+` ~ offset ~ `>\s+at\s+` ~ moduleName); 696 697 if(matched.length < 4) { 698 return frame; 699 } 700 701 frame.address = matched["address"]; 702 frame.name = matched["name"]; 703 frame.moduleName = matched["module"]; 704 frame.offset = matched["offset"]; 705 706 frame.invalid = frame.address == "" || frame.name == "" || frame.moduleName == "" || frame.offset == ""; 707 708 return frame; 709 } 710 711 /// Parse a Linux frame 712 Frame toLinuxFrame(string line) { 713 Frame frame; 714 715 auto matched = matchFirst(line, file ~ `:` ~ linePattern ~ `\s+` ~ name ~ `\s+\[` ~ address ~ `\]`); 716 717 if(matched.length < 4) { 718 return frame; 719 } 720 721 frame.file = matched["file"]; 722 frame.name = matched["name"]; 723 frame.address = matched["address"]; 724 frame.line = matched["line"].to!int; 725 726 frame.invalid = frame.address == "" || frame.name == "" || frame.file == "" || frame.line == 0; 727 728 return frame; 729 } 730 731 /// Parse a Linux frame 732 Frame toMissingInfoLinuxFrame(string line) { 733 Frame frame; 734 735 auto matched = matchFirst(line, `\?\?:\?\s+` ~ name ~ `\s+\[` ~ address ~ `\]`); 736 737 if(matched.length < 2) { 738 return frame; 739 } 740 741 frame.name = matched["name"]; 742 frame.address = matched["address"]; 743 744 frame.invalid = frame.address == "" || frame.name == ""; 745 746 return frame; 747 } 748 749 /// Converts a stack trace line to a Frame structure 750 Frame toFrame(string line) 751 { 752 Frame frame; 753 frame.raw = line; 754 frame.invalid = false; 755 756 auto frames = [ 757 line.toDarwinFrame, 758 line.toWindows1Frame, 759 line.toWindows2Frame, 760 line.toGLibCFrame, 761 line.toNetBsdFrame, 762 line.toLinuxFrame, 763 line.toMissingInfoLinuxFrame, 764 frame 765 ]; 766 767 return frames.filter!(a => !a.invalid).front; 768 } 769 770 @("Get frame info from Darwin platform format") 771 unittest 772 { 773 auto line = "1 ???fluent-asserts 0x00abcdef000000 D6module4funcAFZv + 0"; 774 775 auto frame = line.toFrame; 776 frame.invalid.should.equal(false); 777 frame.index.should.equal(1); 778 frame.moduleName.should.equal("???fluent-asserts"); 779 frame.address.should.equal("0x00abcdef000000"); 780 frame.name.should.equal("D6module4funcAFZv"); 781 frame.offset.should.equal("0"); 782 } 783 784 @("Get frame info from windows platform format without path") 785 unittest 786 { 787 auto line = "0x779CAB5A in RtlInitializeExceptionChain"; 788 789 auto frame = line.toFrame; 790 frame.invalid.should.equal(false); 791 frame.index.should.equal(-1); 792 frame.moduleName.should.equal(""); 793 frame.address.should.equal("0x779CAB5A"); 794 frame.name.should.equal("RtlInitializeExceptionChain"); 795 frame.offset.should.equal(""); 796 } 797 798 @("Get frame info from windows platform format with path") 799 unittest 800 { 801 auto line = `0x00402669 in void app.__unitestL82_8() at D:\tidynumbers\source\app.d(84)`; 802 803 auto frame = line.toFrame; 804 frame.invalid.should.equal(false); 805 frame.index.should.equal(-1); 806 frame.moduleName.should.equal(""); 807 frame.address.should.equal("0x00402669"); 808 frame.name.should.equal("void app.__unitestL82_8()"); 809 frame.file.should.equal(`D:\tidynumbers\source\app.d`); 810 frame.line.should.equal(84); 811 frame.offset.should.equal(""); 812 } 813 814 @("Get frame info from CRuntime_Glibc format without offset") 815 unittest 816 { 817 auto line = `module(_D6module4funcAFZv) [0x00000000]`; 818 819 auto frame = line.toFrame; 820 821 frame.invalid.should.equal(false); 822 frame.moduleName.should.equal("module"); 823 frame.name.should.equal("_D6module4funcAFZv"); 824 frame.address.should.equal("0x00000000"); 825 frame.index.should.equal(-1); 826 frame.offset.should.equal(""); 827 } 828 829 @("Get frame info from CRuntime_Glibc format with offset") 830 unittest 831 { 832 auto line = `module(_D6module4funcAFZv+0x78) [0x00000000]`; 833 834 auto frame = line.toFrame; 835 836 frame.invalid.should.equal(false); 837 frame.moduleName.should.equal("module"); 838 frame.name.should.equal("_D6module4funcAFZv"); 839 frame.address.should.equal("0x00000000"); 840 frame.index.should.equal(-1); 841 frame.offset.should.equal("0x78"); 842 } 843 844 @("Get frame info from NetBSD format") 845 unittest 846 { 847 auto line = `0x00000000 <_D6module4funcAFZv+0x78> at module`; 848 849 auto frame = line.toFrame; 850 851 frame.invalid.should.equal(false); 852 frame.moduleName.should.equal("module"); 853 frame.name.should.equal("_D6module4funcAFZv"); 854 frame.address.should.equal("0x00000000"); 855 frame.index.should.equal(-1); 856 frame.offset.should.equal("0x78"); 857 } 858 859 /// Get the main frame info from linux format 860 unittest { 861 auto line = `generated.d:45 _Dmain [0x8e80c4]`; 862 863 auto frame = line.toFrame; 864 865 frame.invalid.should.equal(false); 866 frame.moduleName.should.equal(""); 867 frame.file.should.equal("generated.d"); 868 frame.line.should.equal(45); 869 frame.name.should.equal("_Dmain"); 870 frame.address.should.equal("0x8e80c4"); 871 frame.index.should.equal(-1); 872 frame.offset.should.equal(""); 873 } 874 875 /// Get a function frame info from linux format 876 unittest { 877 auto line = `lifecycle/trial/runner.d:106 trial.interfaces.SuiteResult[] trial.runner.runTests(const(trial.interfaces.TestCase)[], immutable(char)[]) [0x8b0ec1]`; 878 auto frame = line.toFrame; 879 880 frame.invalid.should.equal(false); 881 frame.moduleName.should.equal(""); 882 frame.file.should.equal("lifecycle/trial/runner.d"); 883 frame.line.should.equal(106); 884 frame.name.should.equal("trial.interfaces.SuiteResult[] trial.runner.runTests(const(trial.interfaces.TestCase)[], immutable(char)[])"); 885 frame.address.should.equal("0x8b0ec1"); 886 frame.index.should.equal(-1); 887 frame.offset.should.equal(""); 888 } 889 890 /// Get an external function frame info from linux format 891 unittest { 892 auto line = `../../.dub/packages/fluent-asserts-0.6.6/fluent-asserts/core/fluentasserts/core/base.d:39 void fluentasserts.core.base.Result.perform() [0x8f4b47]`; 893 auto frame = line.toFrame; 894 895 frame.invalid.should.equal(false); 896 frame.moduleName.should.equal(""); 897 frame.file.should.equal("../../.dub/packages/fluent-asserts-0.6.6/fluent-asserts/core/fluentasserts/core/base.d"); 898 frame.line.should.equal(39); 899 frame.name.should.equal("void fluentasserts.core.base.Result.perform()"); 900 frame.address.should.equal("0x8f4b47"); 901 frame.index.should.equal(-1); 902 frame.offset.should.equal(""); 903 } 904 905 /// Get an external function frame info from linux format 906 unittest { 907 auto line = `lifecycle/trial/discovery/unit.d:268 _D5trial9discovery4unit17UnitTestDiscovery231__T12addTestCasesVAyaa62_2f686f6d652f626f737a2f776f726b73706163652f64746573742f6c6966656379636c652f747269616c2f6578656375746f722f706172616c6c656c2e64VAyaa23_747269616c2e6578656375746f722e706172616c6c656cS245trial8executor8parallelZ12addTestCasesMFZ9__lambda4FZv [0x872000]`; 908 auto frame = line.toFrame; 909 910 frame.invalid.should.equal(false); 911 frame.moduleName.should.equal(""); 912 frame.file.should.equal("lifecycle/trial/discovery/unit.d"); 913 frame.line.should.equal(268); 914 frame.name.should.equal("_D5trial9discovery4unit17UnitTestDiscovery231__T12addTestCasesVAyaa62_2f686f6d652f626f737a2f776f726b73706163652f64746573742f6c6966656379636c652f747269616c2f6578656375746f722f706172616c6c656c2e64VAyaa23_747269616c2e6578656375746f722e706172616c6c656cS245trial8executor8parallelZ12addTestCasesMFZ9__lambda4FZv"); 915 frame.address.should.equal("0x872000"); 916 frame.index.should.equal(-1); 917 frame.offset.should.equal(""); 918 } 919 920 /// Get an missing info function frame info from linux format 921 unittest { 922 auto line = `??:? __libc_start_main [0x174bbf44]`; 923 auto frame = line.toFrame; 924 925 frame.invalid.should.equal(false); 926 frame.moduleName.should.equal(""); 927 frame.file.should.equal(""); 928 frame.line.should.equal(-1); 929 frame.name.should.equal("__libc_start_main"); 930 frame.address.should.equal("0x174bbf44"); 931 frame.index.should.equal(-1); 932 frame.offset.should.equal(""); 933 }