]> git.basschouten.com Git - openhab-addons.git/blob
5bada538f9722a967c71b5f6a3b08df8a60a93a7
[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.network.internal;
14
15 import static org.hamcrest.CoreMatchers.is;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.*;
18 import static org.mockito.ArgumentMatchers.*;
19 import static org.mockito.Mockito.*;
20
21 import java.io.IOException;
22 import java.net.UnknownHostException;
23 import java.util.Collections;
24 import java.util.Optional;
25 import java.util.concurrent.ExecutorService;
26 import java.util.concurrent.TimeUnit;
27 import java.util.function.Consumer;
28
29 import org.junit.jupiter.api.AfterEach;
30 import org.junit.jupiter.api.BeforeEach;
31 import org.junit.jupiter.api.Test;
32 import org.junit.jupiter.api.extension.ExtendWith;
33 import org.mockito.ArgumentCaptor;
34 import org.mockito.Mock;
35 import org.mockito.junit.jupiter.MockitoExtension;
36 import org.mockito.junit.jupiter.MockitoSettings;
37 import org.mockito.quality.Strictness;
38 import org.openhab.binding.network.internal.toberemoved.cache.ExpiringCacheAsync;
39 import org.openhab.binding.network.internal.toberemoved.cache.ExpiringCacheHelper;
40 import org.openhab.binding.network.internal.utils.NetworkUtils;
41 import org.openhab.binding.network.internal.utils.NetworkUtils.ArpPingUtilEnum;
42 import org.openhab.binding.network.internal.utils.NetworkUtils.IpPingMethodEnum;
43 import org.openhab.binding.network.internal.utils.PingResult;
44
45 /**
46  * Tests cases for {@see PresenceDetectionValue}
47  *
48  * @author David Graeff - Initial contribution
49  */
50 @ExtendWith(MockitoExtension.class)
51 @MockitoSettings(strictness = Strictness.LENIENT)
52 public class PresenceDetectionTest {
53     private static final long CACHETIME = 2000L;
54
55     private PresenceDetection subject;
56
57     private @Mock Consumer<PresenceDetectionValue> callback;
58     private @Mock ExecutorService executorService;
59     private @Mock PresenceDetectionListener listener;
60     private @Mock NetworkUtils networkUtils;
61
62     @BeforeEach
63     public void setUp() throws UnknownHostException {
64         // Mock an interface
65         when(networkUtils.getInterfaceNames()).thenReturn(Collections.singleton("TESTinterface"));
66         doReturn(ArpPingUtilEnum.IPUTILS_ARPING).when(networkUtils).determineNativeARPpingMethod(anyString());
67         doReturn(IpPingMethodEnum.WINDOWS_PING).when(networkUtils).determinePingMethod();
68
69         subject = spy(new PresenceDetection(listener, (int) CACHETIME));
70         subject.networkUtils = networkUtils;
71         subject.cache = spy(new ExpiringCacheAsync<>(CACHETIME, () -> {
72             subject.performPresenceDetection(false);
73         }));
74
75         // Set a useful configuration. The default presenceDetection is a no-op.
76         subject.setHostname("127.0.0.1");
77         subject.setTimeout(300);
78         subject.setUseDhcpSniffing(false);
79         subject.setIOSDevice(true);
80         subject.setServicePorts(Collections.singleton(1010));
81         subject.setUseArpPing(true, "arping", ArpPingUtilEnum.IPUTILS_ARPING);
82         subject.setUseIcmpPing(true);
83
84         assertThat(subject.pingMethod, is(IpPingMethodEnum.WINDOWS_PING));
85     }
86
87     @AfterEach
88     public void shutDown() {
89         subject.waitForPresenceDetection();
90     }
91
92     // Depending on the amount of test methods an according amount of threads is spawned.
93     // We will check if they spawn and return in time.
94     @Test
95     public void threadCountTest() {
96         assertNull(subject.executorService);
97
98         doNothing().when(subject).performARPping(any());
99         doNothing().when(subject).performJavaPing();
100         doNothing().when(subject).performSystemPing();
101         doNothing().when(subject).performServicePing(anyInt());
102
103         subject.performPresenceDetection(false);
104
105         // Thread count: ARP + ICMP + 1*TCP
106         assertThat(subject.detectionChecks, is(3));
107         assertNotNull(subject.executorService);
108
109         subject.waitForPresenceDetection();
110         assertThat(subject.detectionChecks, is(0));
111         assertNull(subject.executorService);
112     }
113
114     @Test
115     public void partialAndFinalCallbackTests() throws InterruptedException, IOException {
116         doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).nativePing(eq(IpPingMethodEnum.WINDOWS_PING),
117                 anyString(), anyInt());
118         doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils)
119                 .nativeARPPing(eq(ArpPingUtilEnum.IPUTILS_ARPING), anyString(), anyString(), any(), anyInt());
120         doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).servicePing(anyString(), anyInt(), anyInt());
121
122         assertTrue(subject.performPresenceDetection(false));
123         subject.waitForPresenceDetection();
124
125         verify(subject, times(0)).performJavaPing();
126         verify(subject).performSystemPing();
127         verify(subject).performARPping(any());
128         verify(subject).performServicePing(anyInt());
129
130         verify(listener, times(3)).partialDetectionResult(any());
131         ArgumentCaptor<PresenceDetectionValue> capture = ArgumentCaptor.forClass(PresenceDetectionValue.class);
132         verify(listener, times(1)).finalDetectionResult(capture.capture());
133
134         assertThat(capture.getValue().getSuccessfulDetectionTypes(), is("ARP_PING, ICMP_PING, TCP_CONNECTION"));
135     }
136
137     @Test
138     public void cacheTest() throws InterruptedException, IOException {
139         doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).nativePing(eq(IpPingMethodEnum.WINDOWS_PING),
140                 anyString(), anyInt());
141         doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils)
142                 .nativeARPPing(eq(ArpPingUtilEnum.IPUTILS_ARPING), anyString(), anyString(), any(), anyInt());
143         doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).servicePing(anyString(), anyInt(), anyInt());
144
145         doReturn(executorService).when(subject).getThreadsFor(anyInt());
146
147         // We expect no valid value
148         assertTrue(subject.cache.isExpired());
149         // Get value will issue a PresenceDetection internally.
150         subject.getValue(callback);
151         verify(subject).performPresenceDetection(eq(false));
152         assertNotNull(subject.executorService);
153         // There should be no straight callback yet
154         verify(callback, times(0)).accept(any());
155
156         // Perform the different presence detection threads now
157         ArgumentCaptor<Runnable> capture = ArgumentCaptor.forClass(Runnable.class);
158         verify(executorService, times(3)).execute(capture.capture());
159         for (Runnable r : capture.getAllValues()) {
160             r.run();
161         }
162         // "Wait" for the presence detection to finish
163         subject.waitForPresenceDetection();
164
165         // Although there are multiple partial results and a final result,
166         // the getValue() consumers get the fastest response possible, and only once.
167         verify(callback, times(1)).accept(any());
168
169         // As long as the cache is valid, we can get the result back again
170         subject.getValue(callback);
171         verify(callback, times(2)).accept(any());
172
173         // Invalidate value, we should not get a new callback immediately again
174         subject.cache.invalidateValue();
175         subject.getValue(callback);
176         verify(callback, times(2)).accept(any());
177     }
178
179     @Test
180     public void reuseValueTests() throws InterruptedException, IOException {
181         final long startTime = 1000L;
182         when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(startTime));
183
184         // The PresenceDetectionValue.getLowestLatency() should return the smallest latency
185         PresenceDetectionValue v = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 20);
186         PresenceDetectionValue v2 = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 19);
187         assertEquals(v, v2);
188         assertThat(v.getLowestLatency(), is(19.0));
189
190         // Advance in time but not expire the cache (1ms left)
191         final long almostExpire = startTime + CACHETIME - 1;
192         when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(almostExpire));
193
194         // Updating should reset the expire timer of the cache
195         v2 = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 28);
196         assertEquals(v, v2);
197         assertThat(v2.getLowestLatency(), is(19.0));
198         assertThat(ExpiringCacheHelper.expireTime(subject.cache),
199                 is(TimeUnit.MILLISECONDS.toNanos(almostExpire + CACHETIME)));
200
201         // Cache expire. A new PresenceDetectionValue instance will be returned
202         when(subject.cache.getCurrentNanoTime())
203                 .thenReturn(TimeUnit.MILLISECONDS.toNanos(almostExpire + CACHETIME + CACHETIME + 1));
204         v2 = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 25);
205         assertNotEquals(v, v2);
206         assertThat(v2.getLowestLatency(), is(25.0));
207     }
208 }