]> git.basschouten.com Git - openhab-addons.git/blob
bf9d09fc6a8e765a088ce32ce3461ebd29dbde64
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.time.Duration;
23 import java.util.Set;
24 import java.util.concurrent.ExecutorService;
25 import java.util.concurrent.ScheduledExecutorService;
26 import java.util.function.Consumer;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.extension.ExtendWith;
32 import org.mockito.ArgumentCaptor;
33 import org.mockito.Mock;
34 import org.mockito.junit.jupiter.MockitoExtension;
35 import org.mockito.junit.jupiter.MockitoSettings;
36 import org.mockito.quality.Strictness;
37 import org.openhab.binding.network.internal.utils.NetworkUtils;
38 import org.openhab.binding.network.internal.utils.NetworkUtils.ArpPingUtilEnum;
39 import org.openhab.binding.network.internal.utils.NetworkUtils.IpPingMethodEnum;
40 import org.openhab.binding.network.internal.utils.PingResult;
41
42 /**
43  * Tests cases for {@see PresenceDetectionValue}
44  *
45  * @author David Graeff - Initial contribution
46  */
47 @ExtendWith(MockitoExtension.class)
48 @MockitoSettings(strictness = Strictness.LENIENT)
49 @NonNullByDefault
50 public class PresenceDetectionTest {
51
52     private @NonNullByDefault({}) PresenceDetection subject;
53
54     private @Mock @NonNullByDefault({}) Consumer<PresenceDetectionValue> callback;
55     private @Mock @NonNullByDefault({}) ExecutorService detectionExecutorService;
56     private @Mock @NonNullByDefault({}) ScheduledExecutorService scheduledExecutorService;
57     private @Mock @NonNullByDefault({}) PresenceDetectionListener listener;
58     private @Mock @NonNullByDefault({}) NetworkUtils networkUtils;
59
60     @BeforeEach
61     public void setUp() {
62         // Mock an interface
63         when(networkUtils.getInterfaceNames()).thenReturn(Set.of("TESTinterface"));
64         doReturn(ArpPingUtilEnum.IPUTILS_ARPING).when(networkUtils).determineNativeArpPingMethod(anyString());
65         doReturn(IpPingMethodEnum.WINDOWS_PING).when(networkUtils).determinePingMethod();
66
67         subject = spy(new PresenceDetection(listener, scheduledExecutorService, Duration.ofSeconds(2)));
68         subject.networkUtils = networkUtils;
69
70         // Set a useful configuration. The default presenceDetection is a no-op.
71         subject.setHostname("127.0.0.1");
72         subject.setTimeout(Duration.ofMillis(300));
73         subject.setUseDhcpSniffing(false);
74         subject.setIOSDevice(true);
75         subject.setServicePorts(Set.of(1010));
76         subject.setUseArpPing(true, "arping", ArpPingUtilEnum.IPUTILS_ARPING);
77         subject.setUseIcmpPing(true);
78
79         assertThat(subject.pingMethod, is(IpPingMethodEnum.WINDOWS_PING));
80     }
81
82     // Depending on the amount of test methods an according amount of threads is spawned.
83     // We will check if they spawn and return in time.
84     @Test
85     public void threadCountTest() {
86         assertNull(subject.detectionExecutorService);
87
88         doNothing().when(subject).performArpPing(any(), any());
89         doNothing().when(subject).performJavaPing(any());
90         doNothing().when(subject).performSystemPing(any());
91         doNothing().when(subject).performServicePing(any(), anyInt());
92
93         subject.getValue(callback -> {
94         });
95
96         // Thread count: ARP + ICMP + 1*TCP
97         assertThat(subject.detectionChecks, is(3));
98         assertNotNull(subject.detectionExecutorService);
99
100         // "Wait" for the presence detection to finish
101         ArgumentCaptor<Runnable> runnableCapture = ArgumentCaptor.forClass(Runnable.class);
102         verify(scheduledExecutorService, times(1)).execute(runnableCapture.capture());
103         runnableCapture.getValue().run();
104
105         assertThat(subject.detectionChecks, is(0));
106         assertNull(subject.detectionExecutorService);
107     }
108
109     @Test
110     public void partialAndFinalCallbackTests() throws InterruptedException, IOException {
111         PingResult pingResult = new PingResult(true, Duration.ofMillis(10));
112         doReturn(pingResult).when(networkUtils).nativePing(eq(IpPingMethodEnum.WINDOWS_PING), anyString(), any());
113         doReturn(pingResult).when(networkUtils).nativeArpPing(eq(ArpPingUtilEnum.IPUTILS_ARPING), anyString(),
114                 anyString(), any(), any());
115         doReturn(pingResult).when(networkUtils).servicePing(anyString(), anyInt(), any());
116
117         doReturn(detectionExecutorService).when(subject).getThreadsFor(anyInt());
118
119         subject.performPresenceDetection();
120
121         assertThat(subject.detectionChecks, is(3));
122
123         // Perform the different presence detection threads now
124         ArgumentCaptor<Runnable> capture = ArgumentCaptor.forClass(Runnable.class);
125         verify(detectionExecutorService, times(3)).execute(capture.capture());
126         for (Runnable r : capture.getAllValues()) {
127             r.run();
128         }
129
130         // "Wait" for the presence detection to finish
131         ArgumentCaptor<Runnable> runnableCapture = ArgumentCaptor.forClass(Runnable.class);
132         verify(scheduledExecutorService, times(1)).execute(runnableCapture.capture());
133         runnableCapture.getValue().run();
134
135         assertThat(subject.detectionChecks, is(0));
136
137         verify(subject, times(0)).performJavaPing(any());
138         verify(subject).performSystemPing(any());
139         verify(subject).performArpPing(any(), any());
140         verify(subject).performServicePing(any(), anyInt());
141
142         verify(listener, times(3)).partialDetectionResult(any());
143         ArgumentCaptor<PresenceDetectionValue> pdvCapture = ArgumentCaptor.forClass(PresenceDetectionValue.class);
144         verify(listener, times(1)).finalDetectionResult(pdvCapture.capture());
145
146         assertThat(pdvCapture.getValue().getSuccessfulDetectionTypes(), is("ARP_PING, ICMP_PING, TCP_CONNECTION"));
147     }
148
149     @Test
150     public void cacheTest() throws InterruptedException, IOException {
151         PingResult pingResult = new PingResult(true, Duration.ofMillis(10));
152         doReturn(pingResult).when(networkUtils).nativePing(eq(IpPingMethodEnum.WINDOWS_PING), anyString(), any());
153         doReturn(pingResult).when(networkUtils).nativeArpPing(eq(ArpPingUtilEnum.IPUTILS_ARPING), anyString(),
154                 anyString(), any(), any());
155         doReturn(pingResult).when(networkUtils).servicePing(anyString(), anyInt(), any());
156
157         doReturn(detectionExecutorService).when(subject).getThreadsFor(anyInt());
158
159         // We expect no valid value
160         assertTrue(subject.cache.isExpired());
161         // Get value will issue a PresenceDetection internally.
162         subject.getValue(callback);
163         verify(subject).performPresenceDetection();
164         assertNotNull(subject.detectionExecutorService);
165         // There should be no straight callback yet
166         verify(callback, times(0)).accept(any());
167
168         // Perform the different presence detection threads now
169         ArgumentCaptor<Runnable> capture = ArgumentCaptor.forClass(Runnable.class);
170         verify(detectionExecutorService, times(3)).execute(capture.capture());
171         for (Runnable r : capture.getAllValues()) {
172             r.run();
173         }
174
175         // "Wait" for the presence detection to finish
176         capture = ArgumentCaptor.forClass(Runnable.class);
177         verify(scheduledExecutorService, times(1)).execute(capture.capture());
178         capture.getValue().run();
179
180         // Although there are multiple partial results and a final result,
181         // the getValue() consumers get the fastest response possible, and only once.
182         verify(callback, times(1)).accept(any());
183
184         // As long as the cache is valid, we can get the result back again
185         subject.getValue(callback);
186         verify(callback, times(2)).accept(any());
187
188         // Invalidate value, we should not get a new callback immediately again
189         subject.cache.invalidateValue();
190         subject.getValue(callback);
191         verify(callback, times(2)).accept(any());
192     }
193 }