docs: public static <T extends Throwable> T assertThrows(String message, Class<T> expectedThrowable,
              ThrowingRunnable runnable) {
          try {
              runnable.run();
          } catch (Throwable actualThrown) {
              if (expectedThrowable.isInstance(actualThrown)) {
                  @SuppressWarnings("unchecked") T retVal = (T) actualThrown;
                  return retVal;
              } else {
                  String expected = formatClass(expectedThrowable);
                  Class<? extends Throwable> actualThrowable = actualThrown.getClass();
                  String actual = formatClass(actualThrowable);
                  if (expected.equals(actual)) {
                      // There must be multiple class loaders. Add the identity hash code so the message
                      // doesn't say "expected: java.lang.String<my.package.MyException> ..."
                      expected += "@" + Integer.toHexString(System.identityHashCode(expectedThrowable));
                      actual += "@" + Integer.toHexString(System.identityHashCode(actualThrowable));
                  }
                  String mismatchMessage = buildPrefix(message)
                          + format("unexpected exception type thrown;", expected, actual);
  
                  // The AssertionError(String, Throwable) ctor is only available on JDK7.
                  AssertionError assertionError = new AssertionError(mismatchMessage);
                  assertionError.initCause(actualThrown);
                  throw assertionError;
              }
          }
          String notThrownMessage = buildPrefix(message) + String
                  .format("expected %s to be thrown, but nothing was thrown",
                          formatClass(expectedThrowable));
          throw new AssertionError(notThrownMessage);
      }