| /* |
| * Copyright 2015-2022 the original author or authors. |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License v2.0 which |
| * accompanies this distribution and is available at |
| * |
| * https://www.eclipse.org/legal/epl-v20.html |
| */ |
| |
| package org.junit.jupiter.api; |
| |
| import static java.time.Duration.ofMillis; |
| import static org.assertj.core.api.Assertions.assertThat; |
| import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; |
| import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTimeout; |
| import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| import static org.junit.jupiter.api.Assertions.fail; |
| import static org.junit.jupiter.api.condition.OS.WINDOWS; |
| |
| import java.time.Duration; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.junit.jupiter.api.condition.OS; |
| import org.junit.jupiter.api.function.Executable; |
| import org.junit.platform.commons.util.ExceptionUtils; |
| import org.opentest4j.AssertionFailedError; |
| |
| /** |
| * Unit tests for JUnit Jupiter {@link Assertions}. |
| * |
| * @since 5.0 |
| */ |
| class AssertTimeoutAssertionsTests { |
| |
| private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(OS.current() == WINDOWS ? 1000 : 100); |
| |
| private static ThreadLocal<AtomicBoolean> changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); |
| |
| private final Executable nix = () -> { |
| }; |
| |
| // --- executable ---------------------------------------------------------- |
| |
| @Test |
| void assertTimeoutForExecutableThatCompletesBeforeTheTimeout() { |
| changed.get().set(false); |
| assertTimeout(ofMillis(500), () -> changed.get().set(true)); |
| assertTrue(changed.get().get(), "should have executed in the same thread"); |
| assertTimeout(ofMillis(500), nix, "message"); |
| assertTimeout(ofMillis(500), nix, () -> "message"); |
| } |
| |
| @Test |
| void assertTimeoutForExecutableThatThrowsAnException() { |
| RuntimeException exception = assertThrows(RuntimeException.class, () -> assertTimeout(ofMillis(500), () -> { |
| throw new RuntimeException("not this time"); |
| })); |
| assertMessageEquals(exception, "not this time"); |
| } |
| |
| @Test |
| void assertTimeoutForExecutableThatThrowsAnAssertionFailedError() { |
| AssertionFailedError exception = assertThrows(AssertionFailedError.class, |
| () -> assertTimeout(ofMillis(500), () -> fail("enigma"))); |
| assertMessageEquals(exception, "enigma"); |
| } |
| |
| @Test |
| void assertTimeoutForExecutableThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, |
| () -> assertTimeout(ofMillis(10), this::nap)); |
| assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by"); |
| } |
| |
| @Test |
| void assertTimeoutWithMessageForExecutableThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, |
| () -> assertTimeout(ofMillis(10), this::nap, "Tempus Fugit")); |
| assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); |
| } |
| |
| @Test |
| void assertTimeoutWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, |
| () -> assertTimeout(ofMillis(10), this::nap, () -> "Tempus" + " " + "Fugit")); |
| assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); |
| } |
| |
| // --- supplier ------------------------------------------------------------ |
| |
| @Test |
| void assertTimeoutForSupplierThatCompletesBeforeTheTimeout() { |
| changed.get().set(false); |
| String result = assertTimeout(ofMillis(500), () -> { |
| changed.get().set(true); |
| return "Tempus Fugit"; |
| }); |
| assertTrue(changed.get().get(), "should have executed in the same thread"); |
| assertEquals("Tempus Fugit", result); |
| assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), () -> "Tempus Fugit", "message")); |
| assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), () -> "Tempus Fugit", () -> "message")); |
| } |
| |
| @Test |
| void assertTimeoutForSupplierThatThrowsAnException() { |
| RuntimeException exception = assertThrows(RuntimeException.class, () -> { |
| assertTimeout(ofMillis(500), () -> { |
| ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time")); |
| return "Tempus Fugit"; |
| }); |
| }); |
| assertMessageEquals(exception, "not this time"); |
| } |
| |
| @Test |
| void assertTimeoutForSupplierThatThrowsAnAssertionFailedError() { |
| AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { |
| assertTimeout(ofMillis(500), () -> { |
| fail("enigma"); |
| return "Tempus Fugit"; |
| }); |
| }); |
| assertMessageEquals(exception, "enigma"); |
| } |
| |
| @Test |
| void assertTimeoutForSupplierThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { |
| assertTimeout(ofMillis(10), () -> { |
| nap(); |
| return "Tempus Fugit"; |
| }); |
| }); |
| assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by"); |
| } |
| |
| @Test |
| void assertTimeoutWithMessageForSupplierThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { |
| assertTimeout(ofMillis(10), () -> { |
| nap(); |
| return "Tempus Fugit"; |
| }, "Tempus Fugit"); |
| }); |
| assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); |
| } |
| |
| @Test |
| void assertTimeoutWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { |
| assertTimeout(ofMillis(10), () -> { |
| nap(); |
| return "Tempus Fugit"; |
| }, () -> "Tempus" + " " + "Fugit"); |
| }); |
| assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); |
| } |
| |
| // -- executable - preemptively --- |
| |
| @Test |
| void assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { |
| changed.get().set(false); |
| assertTimeoutPreemptively(ofMillis(500), () -> changed.get().set(true)); |
| assertFalse(changed.get().get(), "should have executed in a different thread"); |
| assertTimeoutPreemptively(ofMillis(500), nix, "message"); |
| assertTimeoutPreemptively(ofMillis(500), nix, () -> "message"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { |
| RuntimeException exception = assertThrows(RuntimeException.class, |
| () -> assertTimeoutPreemptively(ofMillis(500), () -> { |
| throw new RuntimeException("not this time"); |
| })); |
| assertMessageEquals(exception, "not this time"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { |
| AssertionFailedError exception = assertThrows(AssertionFailedError.class, |
| () -> assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"))); |
| assertMessageEquals(exception, "enigma"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, |
| () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt)); |
| assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); |
| assertMessageStartsWith(error.getCause(), "Execution timed out in "); |
| assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, |
| () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, "Tempus Fugit")); |
| assertMessageEquals(error, |
| "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); |
| assertMessageStartsWith(error.getCause(), "Execution timed out in "); |
| assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, |
| () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, |
| () -> "Tempus" + " " + "Fugit")); |
| assertMessageEquals(error, |
| "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); |
| assertMessageStartsWith(error.getCause(), "Execution timed out in "); |
| assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { |
| assertTimeoutPreemptively(ofMillis(500), nix, () -> "Tempus" + " " + "Fugit"); |
| } |
| |
| // -- supplier - preemptively --- |
| |
| @Test |
| void assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { |
| changed.get().set(false); |
| String result = assertTimeoutPreemptively(ofMillis(500), () -> { |
| changed.get().set(true); |
| return "Tempus Fugit"; |
| }); |
| assertFalse(changed.get().get(), "should have executed in a different thread"); |
| assertEquals("Tempus Fugit", result); |
| assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", "message")); |
| assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", () -> "message")); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { |
| RuntimeException exception = assertThrows(RuntimeException.class, () -> { |
| assertTimeoutPreemptively(ofMillis(500), () -> { |
| ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time")); |
| return "Tempus Fugit"; |
| }); |
| }); |
| assertMessageEquals(exception, "not this time"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { |
| AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { |
| assertTimeoutPreemptively(ofMillis(500), () -> { |
| fail("enigma"); |
| return "Tempus Fugit"; |
| }); |
| }); |
| assertMessageEquals(exception, "enigma"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { |
| assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { |
| waitForInterrupt(); |
| return "Tempus Fugit"; |
| }); |
| }); |
| |
| assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); |
| assertMessageStartsWith(error.getCause(), "Execution timed out in "); |
| assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { |
| assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { |
| waitForInterrupt(); |
| return "Tempus Fugit"; |
| }, "Tempus Fugit"); |
| }); |
| |
| assertMessageEquals(error, |
| "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); |
| assertMessageStartsWith(error.getCause(), "Execution timed out in "); |
| assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { |
| AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { |
| assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { |
| waitForInterrupt(); |
| return "Tempus Fugit"; |
| }, () -> "Tempus" + " " + "Fugit"); |
| }); |
| |
| assertMessageEquals(error, |
| "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); |
| assertMessageStartsWith(error.getCause(), "Execution timed out in "); |
| assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); |
| } |
| |
| @Test |
| void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { |
| AtomicReference<String> threadName = new AtomicReference<>(""); |
| assertTimeoutPreemptively(ofMillis(1000), () -> threadName.set(Thread.currentThread().getName())); |
| assertTrue(threadName.get().startsWith("junit-timeout-thread-"), |
| "Thread name does not match the expected prefix"); |
| } |
| |
| /** |
| * Take a nap for 100 milliseconds. |
| */ |
| private void nap() throws InterruptedException { |
| long start = System.currentTimeMillis(); |
| // workaround for imprecise clocks (yes, Windows, I'm talking about you) |
| do { |
| Thread.sleep(100); |
| } while (System.currentTimeMillis() - start < 100); |
| } |
| |
| private void waitForInterrupt() { |
| try { |
| assertFalse(Thread.interrupted(), "Already interrupted"); |
| new CountDownLatch(1).await(); |
| } |
| catch (InterruptedException ignore) { |
| // ignore |
| } |
| } |
| |
| /** |
| * Assert the given stack trace elements contain an element with the given class name and method name. |
| */ |
| private static void assertStackTraceContains(StackTraceElement[] stackTrace, String className, String methodName) { |
| assertThat(stackTrace).anySatisfy(element -> { |
| assertThat(element.getClassName()).endsWith(className); |
| assertThat(element.getMethodName()).isEqualTo(methodName); |
| }); |
| } |
| |
| } |