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