public static @NonNull String printThrowable(@CheckForNull Throwable t) {
        if (t == null) {
            return Messages.Functions_NoExceptionDetails();
        }
        StringBuilder s = new StringBuilder();
        doPrintStackTrace(s, t, null, "", new HashSet<>());
        return s.toString();
    }
@Issue("JDK-6507809")
    @Test public void printThrowable() {
        // Basics: a single exception. No change.
        assertPrintThrowable(new Stack("java.lang.NullPointerException: oops", "p.C.method1:17", "m.Main.main:1"),
            "java.lang.NullPointerException: oops\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "\tat m.Main.main(Main.java:1)\n",
            "java.lang.NullPointerException: oops\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "\tat m.Main.main(Main.java:1)\n");
        // try {…} catch (Exception x) {throw new IllegalStateException(x);}
        assertPrintThrowable(new Stack("java.lang.IllegalStateException: java.lang.NullPointerException: oops", "p.C.method1:19", "m.Main.main:1").
                       cause(new Stack("java.lang.NullPointerException: oops", "p.C.method2:23", "p.C.method1:17", "m.Main.main:1")),
            "java.lang.IllegalStateException: java.lang.NullPointerException: oops\n" +
            "\tat p.C.method1(C.java:19)\n" +
            "\tat m.Main.main(Main.java:1)\n" +
            "Caused by: java.lang.NullPointerException: oops\n" +
            "\tat p.C.method2(C.java:23)\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "\t... 1 more\n",
            "java.lang.NullPointerException: oops\n" +
            "\tat p.C.method2(C.java:23)\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "Caused: java.lang.IllegalStateException\n" +
            "\tat p.C.method1(C.java:19)\n" +
            "\tat m.Main.main(Main.java:1)\n");
        // try {…} catch (Exception x) {throw new IllegalStateException("more info");}
        assertPrintThrowable(new Stack("java.lang.IllegalStateException: more info", "p.C.method1:19", "m.Main.main:1").
                       cause(new Stack("java.lang.NullPointerException: oops", "p.C.method2:23", "p.C.method1:17", "m.Main.main:1")),
            "java.lang.IllegalStateException: more info\n" +
            "\tat p.C.method1(C.java:19)\n" +
            "\tat m.Main.main(Main.java:1)\n" +
            "Caused by: java.lang.NullPointerException: oops\n" +
            "\tat p.C.method2(C.java:23)\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "\t... 1 more\n",
            "java.lang.NullPointerException: oops\n" +
            "\tat p.C.method2(C.java:23)\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "Caused: java.lang.IllegalStateException: more info\n" +
            "\tat p.C.method1(C.java:19)\n" +
            "\tat m.Main.main(Main.java:1)\n");
        // try {…} catch (Exception x) {throw new IllegalStateException("more info: " + x);}
        assertPrintThrowable(new Stack("java.lang.IllegalStateException: more info: java.lang.NullPointerException: oops", "p.C.method1:19", "m.Main.main:1").
                       cause(new Stack("java.lang.NullPointerException: oops", "p.C.method2:23", "p.C.method1:17", "m.Main.main:1")),
            "java.lang.IllegalStateException: more info: java.lang.NullPointerException: oops\n" +
            "\tat p.C.method1(C.java:19)\n" +
            "\tat m.Main.main(Main.java:1)\n" +
            "Caused by: java.lang.NullPointerException: oops\n" +
            "\tat p.C.method2(C.java:23)\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "\t... 1 more\n",
            "java.lang.NullPointerException: oops\n" +
            "\tat p.C.method2(C.java:23)\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "Caused: java.lang.IllegalStateException: more info\n" +
            "\tat p.C.method1(C.java:19)\n" +
            "\tat m.Main.main(Main.java:1)\n");
        // Synthetic stack showing an exception made elsewhere, such as happens with hudson.remoting.Channel.attachCallSiteStackTrace.
        Throwable t = new Stack("remote.Exception: oops", "remote.Place.method:17", "remote.Service.run:9");
        StackTraceElement[] callSite = new Stack("wrapped.Exception", "local.Side.call:11", "local.Main.main:1").getStackTrace();
        StackTraceElement[] original = t.getStackTrace();
        StackTraceElement[] combined = new StackTraceElement[original.length + 1 + callSite.length];
        System.arraycopy(original, 0, combined, 0, original.length);
        combined[original.length] = new StackTraceElement(".....", "remote call", null, -2);
        System.arraycopy(callSite, 0, combined, original.length + 1, callSite.length);
        t.setStackTrace(combined);
        assertPrintThrowable(t,
            "remote.Exception: oops\n" +
            "\tat remote.Place.method(Place.java:17)\n" +
            "\tat remote.Service.run(Service.java:9)\n" +
            "\tat ......remote call(Native Method)\n" +
            "\tat local.Side.call(Side.java:11)\n" +
            "\tat local.Main.main(Main.java:1)\n",
            "remote.Exception: oops\n" +
            "\tat remote.Place.method(Place.java:17)\n" +
            "\tat remote.Service.run(Service.java:9)\n" +
            "\tat ......remote call(Native Method)\n" +
            "\tat local.Side.call(Side.java:11)\n" +
            "\tat local.Main.main(Main.java:1)\n");
        // Same but now using a cause on the remote side.
        t = new Stack("remote.Wrapper: remote.Exception: oops", "remote.Place.method2:19", "remote.Service.run:9").cause(new Stack("remote.Exception: oops", "remote.Place.method1:11", "remote.Place.method2:17", "remote.Service.run:9"));
        callSite = new Stack("wrapped.Exception", "local.Side.call:11", "local.Main.main:1").getStackTrace();
        original = t.getStackTrace();
        combined = new StackTraceElement[original.length + 1 + callSite.length];
        System.arraycopy(original, 0, combined, 0, original.length);
        combined[original.length] = new StackTraceElement(".....", "remote call", null, -2);
        System.arraycopy(callSite, 0, combined, original.length + 1, callSite.length);
        t.setStackTrace(combined);
        assertPrintThrowable(t,
            "remote.Wrapper: remote.Exception: oops\n" +
            "\tat remote.Place.method2(Place.java:19)\n" +
            "\tat remote.Service.run(Service.java:9)\n" +
            "\tat ......remote call(Native Method)\n" +
            "\tat local.Side.call(Side.java:11)\n" +
            "\tat local.Main.main(Main.java:1)\n" +
            "Caused by: remote.Exception: oops\n" +
            "\tat remote.Place.method1(Place.java:11)\n" +
            "\tat remote.Place.method2(Place.java:17)\n" +
            "\tat remote.Service.run(Service.java:9)\n",
            "remote.Exception: oops\n" +
            "\tat remote.Place.method1(Place.java:11)\n" +
            "\tat remote.Place.method2(Place.java:17)\n" +
            "\tat remote.Service.run(Service.java:9)\n" + // we do not know how to elide the common part in this case
            "Caused: remote.Wrapper\n" +
            "\tat remote.Place.method2(Place.java:19)\n" +
            "\tat remote.Service.run(Service.java:9)\n" +
            "\tat ......remote call(Native Method)\n" +
            "\tat local.Side.call(Side.java:11)\n" +
            "\tat local.Main.main(Main.java:1)\n");
        // Suppressed exceptions:
        assertPrintThrowable(new Stack("java.lang.IllegalStateException: java.lang.NullPointerException: oops", "p.C.method1:19", "m.Main.main:1").
                       cause(new Stack("java.lang.NullPointerException: oops", "p.C.method2:23", "p.C.method1:17", "m.Main.main:1")).
                  suppressed(new Stack("java.io.IOException: could not close", "p.C.close:99", "p.C.method1:18", "m.Main.main:1"),
                             new Stack("java.io.IOException: java.lang.NullPointerException", "p.C.flush:77", "p.C.method1:18", "m.Main.main:1").
                       cause(new Stack("java.lang.NullPointerException", "p.C.findFlushee:70", "p.C.flush:75", "p.C.method1:18", "m.Main.main:1"))),
            "java.lang.IllegalStateException: java.lang.NullPointerException: oops\n" +
            "\tat p.C.method1(C.java:19)\n" +
            "\tat m.Main.main(Main.java:1)\n" +
            "\tSuppressed: java.io.IOException: could not close\n" +
            "\t\tat p.C.close(C.java:99)\n" +
            "\t\tat p.C.method1(C.java:18)\n" +
            "\t\t... 1 more\n" +
            "\tSuppressed: java.io.IOException: java.lang.NullPointerException\n" +
            "\t\tat p.C.flush(C.java:77)\n" +
            "\t\tat p.C.method1(C.java:18)\n" +
            "\t\t... 1 more\n" +
            "\tCaused by: java.lang.NullPointerException\n" +
            "\t\tat p.C.findFlushee(C.java:70)\n" +
            "\t\tat p.C.flush(C.java:75)\n" +
            "\t\t... 2 more\n" +
            "Caused by: java.lang.NullPointerException: oops\n" +
            "\tat p.C.method2(C.java:23)\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "\t... 1 more\n",
            "java.lang.NullPointerException: oops\n" +
            "\tat p.C.method2(C.java:23)\n" +
            "\tat p.C.method1(C.java:17)\n" +
            "Also:   java.io.IOException: could not close\n" +
            "\t\tat p.C.close(C.java:99)\n" +
            "\t\tat p.C.method1(C.java:18)\n" +
            "Also:   java.lang.NullPointerException\n" +
            "\t\tat p.C.findFlushee(C.java:70)\n" +
            "\t\tat p.C.flush(C.java:75)\n" +
            "\tCaused: java.io.IOException\n" +
            "\t\tat p.C.flush(C.java:77)\n" +
            "\t\tat p.C.method1(C.java:18)\n" +
            "Caused: java.lang.IllegalStateException\n" +
            "\tat p.C.method1(C.java:19)\n" +
            "\tat m.Main.main(Main.java:1)\n");
        // Custom printStackTrace implementations:
        assertPrintThrowable(new Throwable() {
            @Override
            public void printStackTrace(PrintWriter s) {
                s.println("Some custom exception");
            }
        }, "Some custom exception\n", "Some custom exception\n");
        // Circular references:
        Stack stack1 = new Stack("p.Exc1", "p.C.method1:17");
        Stack stack2 = new Stack("p.Exc2", "p.C.method2:27");
        stack1.cause(stack2);
        stack2.cause(stack1);
        //Format changed in 11.0.9 / 8.0.272 (JDK-8226809 / JDK-8252444 / JDK-8252489)

        if (getVersion().isNewerThanOrEqualTo(new VersionNumber("11.0.9")) ||
                (getVersion().getDigitAt(0) == 8 && getVersion().isNewerThanOrEqualTo(new VersionNumber("8.0.272")))) {
            assertPrintThrowable(stack1,
                    "p.Exc1\n" +
                            "\tat p.C.method1(C.java:17)\n" +
                            "Caused by: p.Exc2\n" +
                            "\tat p.C.method2(C.java:27)\n" +
                            "Caused by: [CIRCULAR REFERENCE: p.Exc1]\n",
                    "<cycle to p.Exc1>\n" +
                            "Caused: p.Exc2\n" +
                            "\tat p.C.method2(C.java:27)\n" +
                            "Caused: p.Exc1\n" +
                            "\tat p.C.method1(C.java:17)\n");
        } else {
            assertPrintThrowable(stack1,
                    "p.Exc1\n" +
                            "\tat p.C.method1(C.java:17)\n" +
                            "Caused by: p.Exc2\n" +
                            "\tat p.C.method2(C.java:27)\n" +
                            "\t[CIRCULAR REFERENCE:p.Exc1]\n",
                    "<cycle to p.Exc1>\n" +
                            "Caused: p.Exc2\n" +
                            "\tat p.C.method2(C.java:27)\n" +
                            "Caused: p.Exc1\n" +
                            "\tat p.C.method1(C.java:17)\n");
        }
    }