| /* |
| * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. |
| * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. |
| */ |
| |
| package test.exceptions |
| |
| import test.supportsSuppressedExceptions |
| import kotlin.test.* |
| |
| class ExceptionTest { |
| private val cause = Exception("cause") |
| |
| @Test fun throwable() = testCreateException(::Throwable, ::Throwable, ::Throwable, ::Throwable) |
| @Test fun error() = testCreateException(::Error, ::Error, ::Error, ::Error) |
| @Test fun exception() = testCreateException(::Exception, ::Exception, ::Exception, ::Exception) |
| @Test fun runtimeException() = testCreateException(::RuntimeException, ::RuntimeException, ::RuntimeException, ::RuntimeException) |
| @Test fun illegalArgumentException() = testCreateException(::IllegalArgumentException, ::IllegalArgumentException, ::IllegalArgumentException, ::IllegalArgumentException) |
| @Test fun illegalStateException() = testCreateException(::IllegalStateException, ::IllegalStateException, ::IllegalStateException, ::IllegalStateException) |
| @Test fun indexOutOfBoundsException() = testCreateException(::IndexOutOfBoundsException, ::IndexOutOfBoundsException) |
| @Test fun unsupportedOperationException() = testCreateException(::UnsupportedOperationException, ::UnsupportedOperationException, ::UnsupportedOperationException, ::UnsupportedOperationException) |
| @Test fun numberFormatException() = testCreateException(::NumberFormatException, ::NumberFormatException) |
| @Test fun nullPointerException() = testCreateException(::NullPointerException, ::NullPointerException) |
| @Test fun classCastException() = testCreateException(::ClassCastException, ::ClassCastException) |
| @Test fun noSuchElementException() = testCreateException(::NoSuchElementException, ::NoSuchElementException) |
| @Test fun concurrentModificationException() = testCreateException(::ConcurrentModificationException, ::ConcurrentModificationException) |
| @Test fun arithmeticException() = testCreateException(::ArithmeticException, ::ArithmeticException) |
| |
| @Test fun noWhenBranchMatchedException() = @Suppress("DEPRECATION_ERROR") testCreateException(::NoWhenBranchMatchedException, ::NoWhenBranchMatchedException, ::NoWhenBranchMatchedException, ::NoWhenBranchMatchedException) |
| @Test fun uninitializedPropertyAccessException() = @Suppress("DEPRECATION_ERROR") testCreateException(::UninitializedPropertyAccessException, ::UninitializedPropertyAccessException, ::UninitializedPropertyAccessException, ::UninitializedPropertyAccessException) |
| |
| @Test fun assertionError() = testCreateException(::AssertionError, ::AssertionError, ::AssertionError) |
| |
| |
| private fun <T : Throwable> testCreateException( |
| noarg: () -> T, |
| fromMessage: (String?) -> T, |
| fromCause: ((Throwable?) -> T)? = null, |
| fromMessageCause: ((String?, Throwable?) -> T)? = null |
| ) { |
| noarg().let { e -> |
| assertEquals(null, e.message) |
| assertEquals(null, e.cause) |
| } |
| |
| fromMessage("message").let { e -> |
| assertEquals("message", e.message) |
| assertEquals(null, e.cause) |
| } |
| |
| fromMessage(null).let { e -> |
| assertTrue(e.message == null || e.message == "null") |
| } |
| |
| fromMessageCause?.run { |
| invoke("message", cause).let { e -> |
| assertEquals("message", e.message) |
| assertSame(cause, e.cause) |
| } |
| invoke(null, null).let { e -> |
| assertEquals(null, e.message) |
| assertEquals(null, e.cause) |
| } |
| } |
| |
| fromCause?.invoke(cause)?.let { e -> |
| assertSame(cause, e.cause) |
| } |
| } |
| |
| @Test |
| fun suppressedExceptions() { |
| val e1 = Throwable() |
| |
| val c1 = Exception("Suppressed 1") |
| val c2 = Exception("Suppressed 2") |
| |
| assertTrue(e1.suppressedExceptions.isEmpty()) |
| |
| e1.addSuppressed(c1) |
| e1.addSuppressed(c2) |
| |
| if (supportsSuppressedExceptions) { |
| assertEquals(listOf(c1, c2), e1.suppressedExceptions) |
| } else { |
| assertTrue(e1.suppressedExceptions.isEmpty()) |
| } |
| } |
| |
| @Test |
| fun exceptionDetailedTrace() { |
| fun root(): Nothing = throw IllegalStateException("Root cause\nDetails: root") |
| |
| fun suppressedError(id: Int): Throwable = UnsupportedOperationException("Side error\nId: $id") |
| |
| fun induced(): Nothing { |
| try { |
| root() |
| } catch (e: Throwable) { |
| for (id in 0..1) |
| e.addSuppressed(suppressedError(id)) |
| throw RuntimeException("Induced", e) |
| } |
| } |
| |
| val e = try { |
| induced() |
| } catch (e: Throwable) { |
| e.apply { addSuppressed(suppressedError(2)) } |
| } |
| |
| val topLevelTrace = e.stackTraceToString() |
| fun assertInTrace(value: Any) { |
| if (value.toString() !in topLevelTrace) { |
| fail("Expected top level trace: $topLevelTrace\n\nto contain: $value") |
| } |
| } |
| |
| assertInTrace(e) |
| |
| val cause = assertNotNull(e.cause, "Should have cause") |
| assertInTrace(cause) |
| |
| if (supportsSuppressedExceptions) { |
| val topLevelSuppressed = e.suppressedExceptions.single() |
| assertInTrace(topLevelSuppressed) |
| cause.suppressedExceptions.forEach { |
| assertInTrace(it) |
| } |
| } |
| |
| // fail(topLevelTrace) // to dump the entire trace |
| } |
| |
| @Test |
| fun circularSuppressedDetailedTrace() { |
| if (!supportsSuppressedExceptions) return |
| |
| // Testing an exception of the following structure |
| // e1 |
| // -- suppressed: e0 (same stack as e1) |
| // -- suppressed: e3 |
| // -- suppressed: e1 |
| // Caused by: e2 |
| // -- suppressed: e1 |
| // Caused by: e3 |
| |
| val e3 = Exception("e3") |
| val e2 = Error("e2", e3) |
| val (e1, e0) = listOf("e1", "e0").map { msg -> RuntimeException(msg, e2.takeIf { msg == "e1" }) } |
| e1.addSuppressed(e0) |
| e1.addSuppressed(e3) |
| e3.addSuppressed(e1) |
| e2.addSuppressed(e1) |
| |
| val topLevelTrace = e1.stackTraceToString() |
| fun assertAppearsInTrace(value: Any, count: Int) { |
| if (Regex.fromLiteral(value.toString()).findAll(topLevelTrace).count() != count) { |
| fail("Expected to find $value $count times in $topLevelTrace") |
| } |
| } |
| assertAppearsInTrace(e1, 3) |
| assertAppearsInTrace(e0, 1) |
| assertAppearsInTrace(e2, 1) |
| assertAppearsInTrace(e3, 2) |
| // fail(topLevelTrace) // to dump the entire trace |
| } |
| |
| } |