]> git.basschouten.com Git - openhab-addons.git/blob
2b98ea8d324cbb5825aec7a1169a0348822d5019
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.junit.Assert.*;
17 import static org.mockito.ArgumentMatchers.*;
18 import static org.mockito.Mockito.*;
19
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;
27
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;
40
41 /**
42  * Tests cases for {@see PresenceDetectionValue}
43  *
44  * @author David Graeff - Initial contribution
45  */
46 public class PresenceDetectionTest {
47     static long CACHETIME = 2000L;
48     @Mock
49     NetworkUtils networkUtils;
50
51     @Mock
52     PresenceDetectionListener listener;
53
54     @Mock
55     ExecutorService executorService;
56
57     @Mock
58     Consumer<PresenceDetectionValue> callback;
59
60     PresenceDetection subject;
61
62     @Before
63     public void setUp() throws UnknownHostException {
64         MockitoAnnotations.initMocks(this);
65
66         // Mock an interface
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();
70
71         subject = spy(new PresenceDetection(listener, (int) CACHETIME));
72         subject.networkUtils = networkUtils;
73         subject.cache = spy(new ExpiringCacheAsync<>(CACHETIME, () -> {
74             subject.performPresenceDetection(false);
75         }));
76
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);
85
86         assertThat(subject.pingMethod, is(IpPingMethodEnum.WINDOWS_PING));
87     }
88
89     @After
90     public void shutDown() {
91         subject.waitForPresenceDetection();
92     }
93
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.
96     @Test
97     public void threadCountTest() {
98         assertNull(subject.executorService);
99
100         doNothing().when(subject).performARPping(any());
101         doNothing().when(subject).performJavaPing();
102         doNothing().when(subject).performSystemPing();
103         doNothing().when(subject).performServicePing(anyInt());
104
105         subject.performPresenceDetection(false);
106
107         // Thread count: ARP + ICMP + 1*TCP
108         assertThat(subject.detectionChecks, is(3));
109         assertNotNull(subject.executorService);
110
111         subject.waitForPresenceDetection();
112         assertThat(subject.detectionChecks, is(0));
113         assertNull(subject.executorService);
114     }
115
116     @Test
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());
123
124         assertTrue(subject.performPresenceDetection(false));
125         subject.waitForPresenceDetection();
126
127         verify(subject, times(0)).performJavaPing();
128         verify(subject).performSystemPing();
129         verify(subject).performARPping(any());
130         verify(subject).performServicePing(anyInt());
131
132         verify(listener, times(3)).partialDetectionResult(any());
133         ArgumentCaptor<PresenceDetectionValue> capture = ArgumentCaptor.forClass(PresenceDetectionValue.class);
134         verify(listener, times(1)).finalDetectionResult(capture.capture());
135
136         assertThat(capture.getValue().getSuccessfulDetectionTypes(), is("ARP_PING, ICMP_PING, TCP_CONNECTION"));
137     }
138
139     @Test
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());
146
147         doReturn(executorService).when(subject).getThreadsFor(anyInt());
148
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());
157
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()) {
162             r.run();
163         }
164         // "Wait" for the presence detection to finish
165         subject.waitForPresenceDetection();
166
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());
170
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());
174
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());
179     }
180
181     @Test
182     public void reuseValueTests() throws InterruptedException, IOException {
183         final long START_TIME = 1000L;
184         when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(START_TIME));
185
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);
189         assertEquals(v, v2);
190         assertThat(v.getLowestLatency(), is(19.0));
191
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));
195
196         // Updating should reset the expire timer of the cache
197         v2 = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 28);
198         assertEquals(v, v2);
199         assertThat(v2.getLowestLatency(), is(19.0));
200         assertThat(ExpiringCacheHelper.expireTime(subject.cache),
201                 is(TimeUnit.MILLISECONDS.toNanos(ALMOST_EXPIRE + CACHETIME)));
202
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));
209     }
210 }