2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bluetooth.util;
15 import static org.junit.jupiter.api.Assertions.*;
17 import java.util.concurrent.CompletableFuture;
18 import java.util.concurrent.CountDownLatch;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.Future;
21 import java.util.concurrent.ScheduledExecutorService;
22 import java.util.concurrent.ScheduledThreadPoolExecutor;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.TimeoutException;
25 import java.util.concurrent.atomic.AtomicInteger;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.junit.jupiter.api.AfterEach;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.openhab.core.common.NamedThreadFactory;
34 * @author Connor Petty - Initial contribution
38 class RetryFutureTest {
40 private static final int TIMEOUT_MS = 1000;
41 private @NonNullByDefault({}) ScheduledExecutorService scheduler;
45 ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1,
46 new NamedThreadFactory("RetryFutureTest", true));
47 scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
48 scheduler.setRemoveOnCancelPolicy(true);
49 this.scheduler = scheduler;
53 public void cleanup() {
54 scheduler.shutdownNow();
58 void callWithRetryNormal() {
59 Future<String> retryFuture = RetryFuture.callWithRetry(() -> "test", scheduler);
61 assertEquals("test", retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
62 } catch (InterruptedException | ExecutionException | TimeoutException e) {
68 void callWithRetry1() {
69 AtomicInteger visitCount = new AtomicInteger();
70 Future<String> retryFuture = RetryFuture.callWithRetry(() -> {
71 if (visitCount.getAndIncrement() == 0) {
72 throw new RetryException(0, TimeUnit.SECONDS);
77 assertEquals("test", retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
78 } catch (InterruptedException | ExecutionException | TimeoutException e) {
84 void composeWithRetryNormal() {
85 CompletableFuture<?> composedFuture = new CompletableFuture<>();
87 Future<?> retryFuture = RetryFuture.composeWithRetry(() -> {
88 composedFuture.complete(null);
89 return composedFuture;
93 retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
94 } catch (InterruptedException | ExecutionException | TimeoutException e) {
97 assertTrue(composedFuture.isDone());
101 void composeWithRetryThrow() {
102 CompletableFuture<?> composedFuture = new CompletableFuture<>();
104 Future<?> retryFuture = RetryFuture.composeWithRetry(() -> {
105 composedFuture.completeExceptionally(new DummyException());
106 return composedFuture;
110 retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
111 } catch (InterruptedException | TimeoutException e) {
113 } catch (ExecutionException ex) {
114 assertTrue(ex.getCause() instanceof DummyException);
116 assertTrue(composedFuture.isDone());
120 void composeWithRetry1() {
121 AtomicInteger visitCount = new AtomicInteger();
122 CompletableFuture<String> composedFuture = new CompletableFuture<>();
123 Future<String> retryFuture = RetryFuture.composeWithRetry(() -> {
124 if (visitCount.getAndIncrement() == 0) {
125 return CompletableFuture.failedFuture(new RetryException(0, TimeUnit.SECONDS));
127 composedFuture.complete("test");
128 return composedFuture;
132 assertEquals("test", retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
133 } catch (InterruptedException | ExecutionException | TimeoutException e) {
136 assertEquals(2, visitCount.get());
137 assertTrue(composedFuture.isDone());
141 void composeWithRetry1Cancel() {
142 CountDownLatch latch = new CountDownLatch(1);
143 AtomicInteger visitCount = new AtomicInteger();
144 CompletableFuture<String> composedFuture = new CompletableFuture<>();
145 Future<String> retryFuture = RetryFuture.composeWithRetry(() -> {
146 if (visitCount.getAndIncrement() == 0) {
147 return CompletableFuture.failedFuture(new RetryException(0, TimeUnit.SECONDS));
150 return composedFuture;
154 if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
155 fail("Timeout while waiting for latch");
157 Future<Boolean> future = scheduler.submit(() -> {
158 retryFuture.cancel(false);
159 return composedFuture.isCancelled();
161 assertTrue(future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
162 } catch (InterruptedException | ExecutionException | TimeoutException e) {
165 assertEquals(2, visitCount.get());
166 assertTrue(composedFuture.isDone());
169 private static class DummyException extends Exception {
170 private static final long serialVersionUID = 1L;