]> git.basschouten.com Git - openhab-addons.git/blob
f29c961fde067b6330e2beefad6dca2fd29cafda
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bluetooth.util;
14
15 import static org.junit.jupiter.api.Assertions.*;
16
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;
26
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;
32
33 /**
34  * @author Connor Petty - Initial contribution
35  *
36  */
37 @NonNullByDefault
38 class RetryFutureTest {
39
40     private static final int TIMEOUT_MS = 1000;
41     private @NonNullByDefault({}) ScheduledExecutorService scheduler;
42
43     @BeforeEach
44     public void init() {
45         ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1,
46                 new NamedThreadFactory("RetryFutureTest", true));
47         scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
48         scheduler.setRemoveOnCancelPolicy(true);
49         this.scheduler = scheduler;
50     }
51
52     @AfterEach
53     public void cleanup() {
54         scheduler.shutdownNow();
55     }
56
57     @Test
58     void callWithRetryNormal() {
59         Future<String> retryFuture = RetryFuture.callWithRetry(() -> "test", scheduler);
60         try {
61             assertEquals("test", retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
62         } catch (InterruptedException | ExecutionException | TimeoutException e) {
63             fail(e);
64         }
65     }
66
67     @Test
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);
73             }
74             return "test";
75         }, scheduler);
76         try {
77             assertEquals("test", retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
78         } catch (InterruptedException | ExecutionException | TimeoutException e) {
79             fail(e);
80         }
81     }
82
83     @Test
84     void composeWithRetryNormal() {
85         CompletableFuture<?> composedFuture = new CompletableFuture<>();
86
87         Future<?> retryFuture = RetryFuture.composeWithRetry(() -> {
88             composedFuture.complete(null);
89             return composedFuture;
90         }, scheduler);
91
92         try {
93             retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
94         } catch (InterruptedException | ExecutionException | TimeoutException e) {
95             fail(e);
96         }
97         assertTrue(composedFuture.isDone());
98     }
99
100     @Test
101     void composeWithRetryThrow() {
102         CompletableFuture<?> composedFuture = new CompletableFuture<>();
103
104         Future<?> retryFuture = RetryFuture.composeWithRetry(() -> {
105             composedFuture.completeExceptionally(new DummyException());
106             return composedFuture;
107         }, scheduler);
108
109         try {
110             retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
111         } catch (InterruptedException | TimeoutException e) {
112             fail(e);
113         } catch (ExecutionException ex) {
114             assertTrue(ex.getCause() instanceof DummyException);
115         }
116         assertTrue(composedFuture.isDone());
117     }
118
119     @Test
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));
126             }
127             composedFuture.complete("test");
128             return composedFuture;
129         }, scheduler);
130
131         try {
132             assertEquals("test", retryFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
133         } catch (InterruptedException | ExecutionException | TimeoutException e) {
134             fail(e);
135         }
136         assertEquals(2, visitCount.get());
137         assertTrue(composedFuture.isDone());
138     }
139
140     @Test
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));
148             }
149             latch.countDown();
150             return composedFuture;
151         }, scheduler);
152
153         try {
154             if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
155                 fail("Timeout while waiting for latch");
156             }
157             Future<Boolean> future = scheduler.submit(() -> {
158                 retryFuture.cancel(false);
159                 return composedFuture.isCancelled();
160             });
161             assertTrue(future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
162         } catch (InterruptedException | ExecutionException | TimeoutException e) {
163             fail(e);
164         }
165         assertEquals(2, visitCount.get());
166         assertTrue(composedFuture.isDone());
167     }
168
169     private static class DummyException extends Exception {
170         private static final long serialVersionUID = 1L;
171     }
172 }