From 0819fce3c3c4cd59bdccd34b1dbe6e15459560b9 Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sat, 25 Oct 2025 23:41:03 +0200 Subject: [PATCH 1/9] Restore boschshc tests Signed-off-by: Ravi Nadahar --- .../discovery/ThingDiscoveryService.java | 12 +- .../devices/bridge/LongPollingTest.java | 135 +----------------- .../discovery/ThingDiscoveryServiceTest.java | 12 +- 3 files changed, 17 insertions(+), 142 deletions(-) diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java index bf4d0f7cd4129..cedba1759e92e 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -117,6 +118,15 @@ public ThingDiscoveryService() { super(BridgeHandler.class, SUPPORTED_THING_TYPES, SEARCH_TIME); } + /** + * Constructor for tests only. + * + * @param scheduler the {@link ScheduledExecutorService} to use during testing. + */ + ThingDiscoveryService(ScheduledExecutorService scheduler) { + super(scheduler, BridgeHandler.class, SUPPORTED_THING_TYPES, SEARCH_TIME, true, null, null); + } + @Override public void initialize() { logger.trace("initialize"); @@ -255,7 +265,7 @@ protected void addDevice(Device device, String roomName) { * Translates a Bosch device ID to an openHAB-compliant thing ID. *

* Characters that are not allowed in thing IDs are replaced by underscores. - * + * * @param deviceId the Bosch device ID * @return the translated openHAB-compliant thing ID */ diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java index da303f6cdb592..ab4ed1d78d535 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java @@ -21,21 +21,12 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.Callable; -import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response.CompleteListener; @@ -58,6 +49,7 @@ import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException; import org.openhab.binding.boschshc.internal.tests.common.CommonTestUtils; +import org.openhab.core.util.SameThreadExecutorService; import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; @@ -72,131 +64,6 @@ @ExtendWith(MockitoExtension.class) class LongPollingTest { - /** - * A dummy implementation of {@link ScheduledFuture}. - *

- * This is required because we can not return null in the executor service test implementation (see - * below). - * - * @author David Pace - Initial contribution - * - * @param The result type returned by this Future - */ - private static class NullScheduledFuture implements ScheduledFuture { - - @Override - public long getDelay(@Nullable TimeUnit unit) { - return 0; - } - - @Override - public int compareTo(@Nullable Delayed o) { - return 0; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return false; - } - - @Override - public boolean isCancelled() { - return false; - } - - @Override - public boolean isDone() { - return false; - } - - @Override - public T get() throws InterruptedException, ExecutionException { - return null; - } - - @Override - public T get(long timeout, @Nullable TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return null; - } - } - - /** - * Executor service implementation that runs all runnables in the same thread in order to enable deterministic - * testing. - * - * @author David Pace - Initial contribution - * - */ - private static class SameThreadExecutorService extends AbstractExecutorService implements ScheduledExecutorService { - - private volatile boolean terminated; - - @Override - public void shutdown() { - terminated = true; - } - - @NonNullByDefault({}) - @Override - public List shutdownNow() { - return Collections.emptyList(); - } - - @Override - public boolean isShutdown() { - return terminated; - } - - @Override - public boolean isTerminated() { - return terminated; - } - - @Override - public boolean awaitTermination(long timeout, @Nullable TimeUnit unit) throws InterruptedException { - shutdown(); - return terminated; - } - - @Override - public void execute(@Nullable Runnable command) { - if (command != null) { - // execute in the same thread in unit tests - command.run(); - } - } - - @Override - public ScheduledFuture schedule(@Nullable Runnable command, long delay, @Nullable TimeUnit unit) { - // not used in this tests - return new NullScheduledFuture<>(); - } - - @Override - public ScheduledFuture schedule(@Nullable Callable callable, long delay, @Nullable TimeUnit unit) { - return new NullScheduledFuture<>(); - } - - @Override - public ScheduledFuture scheduleAtFixedRate(@Nullable Runnable command, long initialDelay, long period, - @Nullable TimeUnit unit) { - if (command != null) { - command.run(); - } - return new NullScheduledFuture<>(); - } - - @Override - public ScheduledFuture scheduleWithFixedDelay(@Nullable Runnable command, long initialDelay, long delay, - @Nullable TimeUnit unit) { - if (command != null) { - command.run(); - } - return new NullScheduledFuture<>(); - } - } - private @NonNullByDefault({}) LongPolling fixture; private @NonNullByDefault({}) BoschHttpClient httpClient; diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryServiceTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryServiceTest.java index 80eec46a59969..4ada102672272 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryServiceTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryServiceTest.java @@ -20,7 +20,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -47,6 +46,7 @@ import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ThingUID; +import org.openhab.core.util.SameThreadExecutorService; /** * Unit tests for {@link ThingDiscoveryService}. @@ -66,7 +66,7 @@ class ThingDiscoveryServiceTest { @BeforeEach void beforeEach() { - fixture = new ThingDiscoveryService(); + fixture = new ThingDiscoveryService(new SameThreadExecutorService()); fixture.addDiscoveryListener(discoveryListener); fixture.setThingHandler(bridgeHandler); } @@ -160,7 +160,7 @@ void testAddDevices() { fixture.addDevices(devices, emptyRooms); // two calls for the two devices expected - verify(discoveryListener, timeout(1000L).times(2)).thingDiscovered(any(), any()); + verify(discoveryListener, times(2)).thingDiscovered(any(), any()); } @Test @@ -186,8 +186,7 @@ void testAddDevice() { device.name = "Test Name"; fixture.addDevice(device, "TestRoom"); - verify(discoveryListener, timeout(1000L)).thingDiscovered(discoveryServiceCaptor.capture(), - discoveryResultCaptor.capture()); + verify(discoveryListener).thingDiscovered(discoveryServiceCaptor.capture(), discoveryResultCaptor.capture()); assertThat(discoveryServiceCaptor.getValue().getClass(), is(ThingDiscoveryService.class)); DiscoveryResult result = discoveryResultCaptor.getValue(); @@ -228,8 +227,7 @@ private void assertDeviceNiceName(String deviceName, String roomName, String exp device.id = "testDevice:ID"; device.name = deviceName; fixture.addDevice(device, roomName); - verify(discoveryListener, timeout(1000L)).thingDiscovered(discoveryServiceCaptor.capture(), - discoveryResultCaptor.capture()); + verify(discoveryListener).thingDiscovered(discoveryServiceCaptor.capture(), discoveryResultCaptor.capture()); assertThat(discoveryServiceCaptor.getValue().getClass(), is(ThingDiscoveryService.class)); DiscoveryResult result = discoveryResultCaptor.getValue(); assertThat(result.getLabel(), is(expectedNiceName)); From 2014478b9aefaf14bb69d877bb12a788b16f6af9 Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sat, 25 Oct 2025 23:50:47 +0200 Subject: [PATCH 2/9] Restore deconz tests Signed-off-by: Ravi Nadahar --- .../internal/discovery/ThingDiscoveryService.java | 10 ++++++++++ .../java/org/openhab/binding/deconz/DeconzTest.java | 7 ++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/discovery/ThingDiscoveryService.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/discovery/ThingDiscoveryService.java index 96b18c7a834e6..10e5066765458 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/discovery/ThingDiscoveryService.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/discovery/ThingDiscoveryService.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -72,6 +73,15 @@ public ThingDiscoveryService() { super(DeconzBridgeHandler.class, SUPPORTED_THING_TYPES_UIDS, 30); } + /** + * Constructor for tests only. + * + * @param scheduler the {@link ScheduledExecutorService} to use during testing. + */ + public ThingDiscoveryService(ScheduledExecutorService scheduler) { + super(scheduler, DeconzBridgeHandler.class, SUPPORTED_THING_TYPES_UIDS, 30, true, null, null); + } + @Override public void startScan() { thingHandler.getBridgeFullState().thenAccept(fullState -> { diff --git a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/DeconzTest.java b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/DeconzTest.java index 269a1ff5bae72..a9db97cd58c02 100644 --- a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/DeconzTest.java +++ b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/DeconzTest.java @@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; import java.io.IOException; import java.io.InputStream; @@ -51,6 +51,7 @@ import org.openhab.core.library.types.DateTimeType; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ThingUID; +import org.openhab.core.util.SameThreadExecutorService; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -92,13 +93,13 @@ public void discoveryTest() throws IOException { Mockito.doAnswer(answer -> CompletableFuture.completedFuture(Optional.of(bridgeFullState))).when(bridgeHandler) .getBridgeFullState(); - ThingDiscoveryService discoveryService = new ThingDiscoveryService(); + ThingDiscoveryService discoveryService = new ThingDiscoveryService(new SameThreadExecutorService()); discoveryService.modified(Map.of(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, false)); discoveryService.setThingHandler(bridgeHandler); discoveryService.initialize(); discoveryService.addDiscoveryListener(discoveryListener); discoveryService.startScan(); - Mockito.verify(discoveryListener, timeout(1000L).times(20)).thingDiscovered(any(), any()); + Mockito.verify(discoveryListener, times(20)).thingDiscovered(any(), any()); } public static T getObjectFromJson(String filename, Class clazz, Gson gson) throws IOException { From be8c6f183833c55a4bcce4bf29de578a61349188 Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sun, 26 Oct 2025 00:10:29 +0200 Subject: [PATCH 3/9] Restore homematic tests Signed-off-by: Ravi Nadahar --- .../HomematicDeviceDiscoveryService.java | 8 +++++++- .../HomematicDeviceDiscoveryServiceTest.java | 19 ++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/discovery/HomematicDeviceDiscoveryService.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/discovery/HomematicDeviceDiscoveryService.java index fc92c1eb1f9f3..a3f17fde9d470 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/discovery/HomematicDeviceDiscoveryService.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/discovery/HomematicDeviceDiscoveryService.java @@ -17,6 +17,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.homematic.internal.common.HomematicConfig; @@ -55,6 +56,11 @@ public HomematicDeviceDiscoveryService() { super(HomematicBridgeHandler.class, Set.of(new ThingTypeUID(BINDING_ID, "-")), DISCOVER_TIMEOUT_SECONDS, false); } + HomematicDeviceDiscoveryService(ScheduledExecutorService scheduler) { + super(scheduler, HomematicBridgeHandler.class, Set.of(new ThingTypeUID(BINDING_ID, "-")), + DISCOVER_TIMEOUT_SECONDS, false, null, null); + } + @Override public void initialize() { thingHandler.setDiscoveryService(this); @@ -149,7 +155,7 @@ private void waitForInstallModeFinished(int timeout) throws InterruptedException private void waitForLoadDevicesFinished() throws InterruptedException, ExecutionException { Future loadFuture; if ((loadFuture = loadDevicesFuture) != null) { - loadFuture.get(); // TODO: Bug - doesn't always complete + loadFuture.get(); } } diff --git a/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/discovery/HomematicDeviceDiscoveryServiceTest.java b/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/discovery/HomematicDeviceDiscoveryServiceTest.java index a218dfd703492..7a60d104d8d5d 100644 --- a/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/discovery/HomematicDeviceDiscoveryServiceTest.java +++ b/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/discovery/HomematicDeviceDiscoveryServiceTest.java @@ -22,7 +22,6 @@ import java.io.IOException; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openhab.binding.homematic.internal.communicator.HomematicGateway; import org.openhab.binding.homematic.internal.handler.HomematicBridgeHandler; @@ -35,6 +34,7 @@ import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.util.SameThreadExecutorService; /** * Tests for {@link HomematicDeviceDiscoveryServiceTest}. @@ -50,7 +50,7 @@ public class HomematicDeviceDiscoveryServiceTest extends JavaTest { @BeforeEach public void setup() throws IOException { this.homematicBridgeHandler = mockHomematicBridgeHandler(); - this.homematicDeviceDiscoveryService = new HomematicDeviceDiscoveryService(); + this.homematicDeviceDiscoveryService = new HomematicDeviceDiscoveryService(new SameThreadExecutorService()); this.homematicDeviceDiscoveryService.setThingHandler(homematicBridgeHandler); } @@ -79,7 +79,6 @@ private HomematicTypeGenerator mockHomematicTypeGenerator() { return mock(HomematicTypeGenerator.class); } - @Disabled @Test public void testDiscoveryResultIsReportedForNewDevice() { SimpleDiscoveryListener discoveryListener = new SimpleDiscoveryListener(); @@ -92,7 +91,6 @@ public void testDiscoveryResultIsReportedForNewDevice() { discoveryResultMatchesHmDevice(discoveryListener.discoveredResults.element(), hmDevice); } - @Disabled @Test public void testDevicesAreLoadedFromBridgeDuringDiscovery() throws IOException { startScanAndWaitForLoadedDevices(); @@ -100,7 +98,6 @@ public void testDevicesAreLoadedFromBridgeDuringDiscovery() throws IOException { verify(homematicBridgeHandler.getGateway()).loadAllDeviceMetadata(); } - @Disabled @Test public void testInstallModeIsNotActiveDuringInitialDiscovery() throws IOException { startScanAndWaitForLoadedDevices(); @@ -108,7 +105,6 @@ public void testInstallModeIsNotActiveDuringInitialDiscovery() throws IOExceptio verify(homematicBridgeHandler.getGateway(), never()).setInstallMode(eq(true), anyInt()); } - @Disabled @Test public void testInstallModeIsActiveDuringSubsequentDiscovery() throws IOException { homematicBridgeHandler.getThing() @@ -116,26 +112,23 @@ public void testInstallModeIsActiveDuringSubsequentDiscovery() throws IOExceptio startScanAndWaitForLoadedDevices(); - verify(homematicBridgeHandler.getGateway(), after(500L)).setInstallMode(true, 60); + verify(homematicBridgeHandler.getGateway()).setInstallMode(true, 60); } - @Disabled @Test - public void testStoppingDiscoveryDisablesInstallMode() throws IOException, InterruptedException { + public void testStoppingDiscoveryDisablesInstallMode() throws IOException { homematicBridgeHandler.getThing() .setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, "")); homematicDeviceDiscoveryService.startScan(); - Thread.sleep(500L); - homematicDeviceDiscoveryService.stopScan(); - verify(homematicBridgeHandler.getGateway(), after(500L)).setInstallMode(false, 0); + verify(homematicBridgeHandler.getGateway()).setInstallMode(false, 0); } private void startScanAndWaitForLoadedDevices() { homematicDeviceDiscoveryService.startScan(); - waitForAssert(() -> verify(homematicBridgeHandler, after(500L)).setOfflineStatus(), 1000, 50); + waitForAssert(() -> verify(homematicBridgeHandler).setOfflineStatus(), 1000, 50); } private void discoveryResultMatchesHmDevice(DiscoveryResult result, HmDevice device) { From 89da3aac9f7c3bccb6c4d0a37a665239025ffd6d Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sun, 26 Oct 2025 00:18:14 +0200 Subject: [PATCH 4/9] Restore mspa tests Signed-off-by: Ravi Nadahar --- .../mspa/internal/discovery/MSpaDiscoveryService.java | 10 ++++++++++ .../java/org/openhab/binding/mspa/TestMessages.java | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.mspa/src/main/java/org/openhab/binding/mspa/internal/discovery/MSpaDiscoveryService.java b/bundles/org.openhab.binding.mspa/src/main/java/org/openhab/binding/mspa/internal/discovery/MSpaDiscoveryService.java index 3c3ed61ae3ba8..8735b101ee5de 100644 --- a/bundles/org.openhab.binding.mspa/src/main/java/org/openhab/binding/mspa/internal/discovery/MSpaDiscoveryService.java +++ b/bundles/org.openhab.binding.mspa/src/main/java/org/openhab/binding/mspa/internal/discovery/MSpaDiscoveryService.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.mspa.internal.MSpaConstants; @@ -44,6 +45,15 @@ public MSpaDiscoveryService() { super(Set.of(MSpaConstants.THING_TYPE_POOL), 0, false); } + /** + * Constructor for tests only. + * + * @param scheduler the {@link ScheduledExecutorService} to use during testing. + */ + public MSpaDiscoveryService(ScheduledExecutorService scheduler) { + super(scheduler, Set.of(MSpaConstants.THING_TYPE_POOL), 0, false, null, null); + } + @Override protected void startScan() { accountList.forEach(account -> { diff --git a/bundles/org.openhab.binding.mspa/src/test/java/org/openhab/binding/mspa/TestMessages.java b/bundles/org.openhab.binding.mspa/src/test/java/org/openhab/binding/mspa/TestMessages.java index 524af41831a33..d2676f7af8399 100644 --- a/bundles/org.openhab.binding.mspa/src/test/java/org/openhab/binding/mspa/TestMessages.java +++ b/bundles/org.openhab.binding.mspa/src/test/java/org/openhab/binding/mspa/TestMessages.java @@ -47,6 +47,7 @@ import org.openhab.core.thing.internal.BridgeImpl; import org.openhab.core.thing.internal.ThingImpl; import org.openhab.core.types.State; +import org.openhab.core.util.SameThreadExecutorService; /** * {@link TestMessages} tests some generic use cases @@ -87,10 +88,10 @@ void testToken() { } @Test - void testDiscovery() throws InterruptedException { + void testDiscovery() { Bridge thing = new BridgeImpl(THING_TYPE_OWNER_ACCOUNT, new ThingUID("mspa", "account")); Map configMap = new HashMap<>(); - MSpaDiscoveryService discovery = new MSpaDiscoveryService(); + MSpaDiscoveryService discovery = new MSpaDiscoveryService(new SameThreadExecutorService()); DiscoveryListenerMock discoveryListener = new DiscoveryListenerMock(); discovery.addDiscoveryListener(discoveryListener); configMap.put("email", "a@b.c"); @@ -102,7 +103,6 @@ void testDiscovery() throws InterruptedException { try { String content = new String(Files.readAllBytes(Paths.get(fileName))); account.decodeDevices(content); - Thread.sleep(500L); List results = discoveryListener.getResults(); assertEquals(1, results.size(), "Number of discovery results"); DiscoveryResult result = results.get(0); From 58bf3aff40fed80e6d2bb744877016c405a50b40 Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sun, 26 Oct 2025 00:35:09 +0200 Subject: [PATCH 5/9] Restore salus tests Signed-off-by: Ravi Nadahar --- .../salus/internal/discovery/SalusDiscovery.java | 13 +++++++++++++ .../internal/discovery/SalusDiscoveryTest.java | 8 +++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.salus/src/main/java/org/openhab/binding/salus/internal/discovery/SalusDiscovery.java b/bundles/org.openhab.binding.salus/src/main/java/org/openhab/binding/salus/internal/discovery/SalusDiscovery.java index 18254c87dbb3c..512d2461488f7 100644 --- a/bundles/org.openhab.binding.salus/src/main/java/org/openhab/binding/salus/internal/discovery/SalusDiscovery.java +++ b/bundles/org.openhab.binding.salus/src/main/java/org/openhab/binding/salus/internal/discovery/SalusDiscovery.java @@ -17,6 +17,7 @@ import java.util.Locale; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.salus.internal.handler.CloudApi; @@ -46,6 +47,18 @@ public SalusDiscovery(CloudApi cloudApi, ThingUID bridgeUid) throws IllegalArgum this.bridgeUid = bridgeUid; } + /** + * Constructor for tests only. + * + * @param scheduler the {@link ScheduledExecutorService} to use during testing. + */ + SalusDiscovery(ScheduledExecutorService scheduler, CloudApi cloudApi, ThingUID bridgeUid) + throws IllegalArgumentException { + super(scheduler, SUPPORTED_THING_TYPES_UIDS, 10, true, null, null); + this.cloudApi = cloudApi; + this.bridgeUid = bridgeUid; + } + @Override protected void startScan() { try { diff --git a/bundles/org.openhab.binding.salus/src/test/java/org/openhab/binding/salus/internal/discovery/SalusDiscoveryTest.java b/bundles/org.openhab.binding.salus/src/test/java/org/openhab/binding/salus/internal/discovery/SalusDiscoveryTest.java index cff407c941231..eff9a720a405c 100644 --- a/bundles/org.openhab.binding.salus/src/test/java/org/openhab/binding/salus/internal/discovery/SalusDiscoveryTest.java +++ b/bundles/org.openhab.binding.salus/src/test/java/org/openhab/binding/salus/internal/discovery/SalusDiscoveryTest.java @@ -28,7 +28,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.openhab.binding.salus.internal.handler.CloudApi; @@ -36,6 +35,7 @@ import org.openhab.binding.salus.internal.rest.exceptions.SalusApiException; import org.openhab.core.config.discovery.DiscoveryListener; import org.openhab.core.thing.ThingUID; +import org.openhab.core.util.SameThreadExecutorService; /** * @author Martin GrzeĊ›lowski - Initial contribution @@ -43,14 +43,13 @@ @NonNullByDefault public class SalusDiscoveryTest { - @Disabled @Test @DisplayName("Method filters out disconnected devices and adds connected devices as things using addThing method") void testFiltersOutDisconnectedDevicesAndAddsConnectedDevicesAsThings() throws Exception { // Given var cloudApi = mock(CloudApi.class); var bridgeUid = new ThingUID("salus", "salus-device", "boo"); - var discoveryService = new SalusDiscovery(cloudApi, bridgeUid); + var discoveryService = new SalusDiscovery(new SameThreadExecutorService(), cloudApi, bridgeUid); var discoveryListener = mock(DiscoveryListener.class); discoveryService.addDiscoveryListener(discoveryListener); var device1 = randomDevice(true); @@ -76,14 +75,13 @@ void testFiltersOutDisconnectedDevicesAndAddsConnectedDevicesAsThings() throws E argThat(discoveryResult -> discoveryResult.getLabel().equals(device4.name()))); } - @Disabled @Test @DisplayName("Cloud API throws an exception during device retrieval, method logs the error") void testLogsErrorWhenCloudApiThrowsException() throws Exception { // Given var cloudApi = mock(CloudApi.class); var bridgeUid = mock(ThingUID.class); - var discoveryService = new SalusDiscovery(cloudApi, bridgeUid); + var discoveryService = new SalusDiscovery(new SameThreadExecutorService(), cloudApi, bridgeUid); given(cloudApi.findDevices()).willThrow(new SalusApiException("API error")); From 8e9a77e88385b54db2e447dc863bacb84d6d5727 Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sun, 26 Oct 2025 00:55:35 +0200 Subject: [PATCH 6/9] Restore satel tests Signed-off-by: Ravi Nadahar --- .../SatelDeviceDiscoveryService.java | 13 +++++ .../SatelDeviceDiscoveryServiceTest.java | 55 +++++++++++-------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/internal/discovery/SatelDeviceDiscoveryService.java b/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/internal/discovery/SatelDeviceDiscoveryService.java index 4f1c039adfdb1..b2114b3c9f2f4 100644 --- a/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/internal/discovery/SatelDeviceDiscoveryService.java +++ b/bundles/org.openhab.binding.satel/src/main/java/org/openhab/binding/satel/internal/discovery/SatelDeviceDiscoveryService.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -68,6 +69,18 @@ public SatelDeviceDiscoveryService(SatelBridgeHandler bridgeHandler, this.thingTypeProvider = thingTypeProvider; } + /** + * Constructor for tests only. + * + * @param scheduler the {@link ScheduledExecutorService} to use during testing. + */ + SatelDeviceDiscoveryService(ScheduledExecutorService scheduler, SatelBridgeHandler bridgeHandler, + Function thingTypeProvider) { + super(SUPPORTED_THING_TYPES, 60, false); + this.bridgeHandler = bridgeHandler; + this.thingTypeProvider = thingTypeProvider; + } + @Override protected void startScan() { scanStopped = false; diff --git a/bundles/org.openhab.binding.satel/src/test/java/org/openhab/binding/satel/internal/discovery/SatelDeviceDiscoveryServiceTest.java b/bundles/org.openhab.binding.satel/src/test/java/org/openhab/binding/satel/internal/discovery/SatelDeviceDiscoveryServiceTest.java index d6972ba27c053..bc46dca980c3c 100644 --- a/bundles/org.openhab.binding.satel/src/test/java/org/openhab/binding/satel/internal/discovery/SatelDeviceDiscoveryServiceTest.java +++ b/bundles/org.openhab.binding.satel/src/test/java/org/openhab/binding/satel/internal/discovery/SatelDeviceDiscoveryServiceTest.java @@ -22,16 +22,15 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.function.Function; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand; @@ -39,10 +38,12 @@ import org.openhab.binding.satel.internal.handler.SatelBridgeHandler; import org.openhab.binding.satel.internal.protocol.SatelMessage; import org.openhab.binding.satel.internal.types.IntegraType; +import org.openhab.core.config.discovery.DiscoveryListener; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.internal.BridgeImpl; import org.openhab.core.thing.type.ThingType; +import org.openhab.core.util.SameThreadExecutorService; /** * @author Krzysztof Goworek - Initial contribution @@ -61,27 +62,20 @@ class SatelDeviceDiscoveryServiceTest { @Mock private EventDispatcher eventDispatcher; - private final List results = new ArrayList<>(); + @Mock + private DiscoveryListener listener; + + @Mock + private SameThreadExecutorService scheduler; + @InjectMocks private SatelDeviceDiscoveryService testSubject; @BeforeEach void setUp() { when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I24); when(bridgeHandler.getEncoding()).thenReturn(bridgeEncoding); - - testSubject = new SatelDeviceDiscoveryService(bridgeHandler, thingTypeProvider) { - @NonNullByDefault - @Override - protected void thingDiscovered(DiscoveryResult discoveryResult) { - results.add(discoveryResult); - } - }; - } - - @AfterEach - void tearDown() { - results.clear(); + testSubject.addDiscoveryListener(listener); } @Test @@ -90,7 +84,7 @@ void startScanShouldNotAddAnyThingWhenBridgeIsNotInitialized() { testSubject.startScan(); - assertEquals(0, results.size()); + verifyNoInteractions(listener); } @Test @@ -102,6 +96,10 @@ void startScanShouldAddVirtualThingsWhenBridgeIsInitialized() { testSubject.startScan(); + ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class); + verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture()); + List results = resultCaptor.getAllValues(); + assertEquals(2, results.size()); assertEquals(THING_TYPE_SYSTEM, results.get(0).getThingTypeUID()); assertEquals(THING_TYPE_EVENTLOG, results.get(1).getThingTypeUID()); @@ -113,7 +111,7 @@ void startScanShouldContinueWhenFailureOccurred() { testSubject.startScan(); - assertEquals(0, results.size()); + verifyNoInteractions(listener); verify(bridgeHandler, times(52)).sendCommand(any(), eq(false)); } @@ -125,6 +123,9 @@ void startScanShouldAddAllDevices() { testSubject.startScan(); + ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class); + verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture()); + List results = resultCaptor.getAllValues(); assertEquals(4, results.stream().filter(result -> THING_TYPE_PARTITION.equals(result.getThingTypeUID())).count()); assertEquals(24, results.stream().filter(result -> THING_TYPE_ZONE.equals(result.getThingTypeUID())).count()); @@ -145,10 +146,12 @@ void startScanShouldAddShutters() { testSubject.startScan(); - List shutterResults = results.stream() + ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class); + verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture()); + List results = resultCaptor.getAllValues().stream() .filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).toList(); - assertEquals(24, shutterResults.size()); - for (DiscoveryResult result : shutterResults) { + assertEquals(24, results.size()); + for (DiscoveryResult result : results) { assertEquals("Device", result.getLabel()); assertEquals(bridge.getUID(), result.getBridgeUID()); assertEquals(2, result.getProperties().size()); @@ -162,6 +165,9 @@ void startScanShouldSkipUnusedOutput() { testSubject.startScan(); + ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class); + verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture()); + List results = resultCaptor.getAllValues(); assertEquals(0, results.stream().filter(result -> THING_TYPE_OUTPUT.equals(result.getThingTypeUID())).count()); assertEquals(0, results.stream().filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).count()); } @@ -173,6 +179,9 @@ void startScanShouldSkipSecondShutterOutput() { testSubject.startScan(); + ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class); + verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture()); + List results = resultCaptor.getAllValues(); assertEquals(0, results.stream().filter(result -> THING_TYPE_OUTPUT.equals(result.getThingTypeUID())).count()); assertEquals(0, results.stream().filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).count()); } @@ -198,7 +207,7 @@ void stopScanShouldSkipDiscovery() throws InterruptedException { thread.join(); verifyNoMoreInteractions(bridgeHandler); - assertEquals(0, results.size()); + verifyNoInteractions(listener); } private void setUpCommandResponse(int deviceKind) { From d7344658c16cd759218c80110c78775d954f0336 Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sun, 26 Oct 2025 01:01:37 +0200 Subject: [PATCH 7/9] Restore tradfri tests Signed-off-by: Ravi Nadahar --- .../discovery/TradfriDiscoveryService.java | 10 +++++++ .../TradfriDiscoveryServiceTest.java | 30 +++++++------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryService.java b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryService.java index ca593c42873ce..44542b4aada1a 100644 --- a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryService.java +++ b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryService.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -71,6 +72,15 @@ public TradfriDiscoveryService() { super(TradfriGatewayHandler.class, SUPPORTED_DEVICE_TYPES_UIDS, 10, true); } + /** + * Constructor for tests only. + * + * @param scheduler the {@link ScheduledExecutorService} to use during testing. + */ + TradfriDiscoveryService(ScheduledExecutorService scheduler) { + super(scheduler, TradfriGatewayHandler.class, SUPPORTED_DEVICE_TYPES_UIDS, 10, true, null, null); + } + @Override protected void startScan() { thingHandler.startScan(); diff --git a/bundles/org.openhab.binding.tradfri/src/test/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryServiceTest.java b/bundles/org.openhab.binding.tradfri/src/test/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryServiceTest.java index 7f9d70d3dbd77..a32c130966a91 100644 --- a/bundles/org.openhab.binding.tradfri/src/test/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryServiceTest.java +++ b/bundles/org.openhab.binding.tradfri/src/test/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryServiceTest.java @@ -38,6 +38,7 @@ import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.builder.BridgeBuilder; +import org.openhab.core.util.SameThreadExecutorService; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -82,7 +83,7 @@ public Collection removeOlderResults(DiscoveryService source, Instant public void setUp() { when(handler.getThing()).thenReturn(BridgeBuilder.create(GATEWAY_TYPE_UID, "1").build()); - discovery = new TradfriDiscoveryService(); + discovery = new TradfriDiscoveryService(new SameThreadExecutorService()); discovery.setThingHandler(handler); discovery.addDiscoveryListener(listener); } @@ -108,13 +109,12 @@ public void correctSupportedTypes() { } @Test - public void validDiscoveryResultWhiteLightW() throws InterruptedException { + public void validDiscoveryResultWhiteLightW() { String json = "{\"9001\":\"TRADFRI bulb E27 W opal 1000lm\",\"9002\":1492856270,\"9020\":1507194357,\"9003\":65537,\"3311\":[{\"5850\":1,\"5851\":254,\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 W opal 1000lm\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":1}}"; JsonObject data = JsonParser.parseString(json).getAsJsonObject(); discovery.onUpdate("65537", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0100:1:65537"))); @@ -125,13 +125,12 @@ public void validDiscoveryResultWhiteLightW() throws InterruptedException { } @Test - public void validDiscoveryResultWhiteLightWS() throws InterruptedException { + public void validDiscoveryResultWhiteLightWS() { String json = "{\"9001\":\"TRADFRI bulb E27 WS opal 980lm\",\"9002\":1492955148,\"9020\":1507200447,\"9003\":65537,\"3311\":[{\"5710\":26909,\"5850\":1,\"5851\":203,\"5707\":0,\"5708\":0,\"5709\":30140,\"5711\":370,\"5706\":\"f1e0b5\",\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 WS opal 980lm\",\"2\":\"\",\"3\":\"1.2.217\",\"6\":1}}"; JsonObject data = JsonParser.parseString(json).getAsJsonObject(); discovery.onUpdate("65537", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0220:1:65537"))); @@ -142,7 +141,7 @@ public void validDiscoveryResultWhiteLightWS() throws InterruptedException { } @Test - public void validDiscoveryResultWhiteLightWSWithIncompleteJson() throws InterruptedException { + public void validDiscoveryResultWhiteLightWSWithIncompleteJson() { // We do not always receive a COLOR = "5706" attribute, even the light supports it - but the gateway does not // seem to have this information, if the bulb is unreachable. String json = "{\"9001\":\"TRADFRI bulb E27 WS opal 980lm\",\"9002\":1492955148,\"9020\":1506968670,\"9003\":65537,\"3311\":[{\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":0,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 WS opal 980lm\",\"2\":\"\",\"3\":\"1.2.217\",\"6\":1}}"; @@ -150,7 +149,6 @@ public void validDiscoveryResultWhiteLightWSWithIncompleteJson() throws Interrup discovery.onUpdate("65537", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0220:1:65537"))); @@ -161,13 +159,12 @@ public void validDiscoveryResultWhiteLightWSWithIncompleteJson() throws Interrup } @Test - public void validDiscoveryResultColorLightCWS() throws InterruptedException { + public void validDiscoveryResultColorLightCWS() { String json = "{\"9001\":\"TRADFRI bulb E27 CWS opal 600lm\",\"9002\":1505151864,\"9020\":1505433527,\"9003\":65550,\"9019\":1,\"9054\":0,\"5750\":2,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 CWS opal 600lm\",\"2\":\"\",\"3\":\"1.3.002\",\"6\":1},\"3311\":[{\"5850\":1,\"5708\":0,\"5851\":254,\"5707\":0,\"5709\":33137,\"5710\":27211,\"5711\":0,\"5706\":\"efd275\",\"9003\":0}]}"; JsonObject data = JsonParser.parseString(json).getAsJsonObject(); discovery.onUpdate("65550", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0210:1:65550"))); @@ -178,13 +175,12 @@ public void validDiscoveryResultColorLightCWS() throws InterruptedException { } @Test - public void validDiscoveryResultAlternativeColorLightCWS() throws InterruptedException { + public void validDiscoveryResultAlternativeColorLightCWS() { String json = "{\"3311\":[{\"5850\":1,\"5709\":32886,\"5851\":216,\"5707\":5309,\"5708\":52400,\"5710\":27217,\"5706\":\"efd275\",\"9003\":0}],\"9001\":\"Mushroom lamp\",\"9002\":1571036916,\"9020\":1571588312,\"9003\":65539,\"9054\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 C\\/WS opal 600\",\"2\":\"\",\"3\":\"1.3.009\",\"6\":1},\"5750\":2}"; JsonObject data = JsonParser.parseString(json).getAsJsonObject(); discovery.onUpdate("65539", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0210:1:65539"))); @@ -195,13 +191,12 @@ public void validDiscoveryResultAlternativeColorLightCWS() throws InterruptedExc } @Test - public void validDiscoveryResultRemoteControl() throws InterruptedException { + public void validDiscoveryResultRemoteControl() { String json = "{\"9001\":\"TRADFRI remote control\",\"9002\":1492843083,\"9020\":1506977986,\"9003\":65536,\"9054\":0,\"5750\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI remote control\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":47},\"15009\":[{\"9003\":0}]}"; JsonObject data = JsonParser.parseString(json).getAsJsonObject(); discovery.onUpdate("65536", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0830:1:65536"))); @@ -212,13 +207,12 @@ public void validDiscoveryResultRemoteControl() throws InterruptedException { } @Test - public void validDiscoveryResultWirelessDimmer() throws InterruptedException { + public void validDiscoveryResultWirelessDimmer() { String json = "{\"9001\":\"TRADFRI wireless dimmer\",\"9002\":1492843083,\"9020\":1506977986,\"9003\":65536,\"9054\":0,\"5750\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI wireless dimmer\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":47},\"15009\":[{\"9003\":0}]}"; JsonObject data = JsonParser.parseString(json).getAsJsonObject(); discovery.onUpdate("65536", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0820:1:65536"))); @@ -229,13 +223,12 @@ public void validDiscoveryResultWirelessDimmer() throws InterruptedException { } @Test - public void validDiscoveryResultMotionSensor() throws InterruptedException { + public void validDiscoveryResultMotionSensor() { String json = "{\"9001\":\"TRADFRI motion sensor\",\"9002\":1492955083,\"9020\":1507120083,\"9003\":65538,\"9054\":0,\"5750\":4,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI motion sensor\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":60},\"3300\":[{\"9003\":0}]}"; JsonObject data = JsonParser.parseString(json).getAsJsonObject(); discovery.onUpdate("65538", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0107:1:65538"))); @@ -246,13 +239,12 @@ public void validDiscoveryResultMotionSensor() throws InterruptedException { } @Test - public void validDiscoveryResultAirPurifier() throws InterruptedException { + public void validDiscoveryResultAirPurifier() { String json = "{\"3\":{\"0\":\"IKEAofSweden\",\"1\":\"STARKVINDAirpurifier\",\"2\":\"\",\"3\":\"1.0.033\",\"6\":1,\"7\":4364},\"5750\":10,\"9001\":\"Luftreiniger\",\"9002\":1633096623,\"9003\":65548,\"9019\":1,\"9020\":1633096633,\"9054\":0,\"15025\":[{\"5900\":1,\"5902\":2,\"5903\":0,\"5904\":259200,\"5905\":0,\"5906\":0,\"5907\":5,\"5908\":10,\"5909\":2,\"5910\":259198,\"9003\":0}]}"; JsonObject data = JsonParser.parseString(json).getAsJsonObject(); discovery.onUpdate("65548", data); - Thread.sleep(500L); assertNotNull(discoveryResult); assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0007:1:65548"))); From b978ff08995f0db6e4e52039ce08e9652f153560 Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sun, 26 Oct 2025 01:12:33 +0200 Subject: [PATCH 8/9] Restore tplinksmarthome tests Signed-off-by: Ravi Nadahar --- .../internal/TPLinkSmartHomeDiscoveryService.java | 14 ++++++++++++++ .../TPLinkSmartHomeDiscoveryServiceTest.java | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeDiscoveryService.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeDiscoveryService.java index 6772dd9f78f6c..1dbd79dbe85e2 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeDiscoveryService.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeDiscoveryService.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -75,6 +76,19 @@ public TPLinkSmartHomeDiscoveryService() throws UnknownHostException { Connection.TP_LINK_SMART_HOME_PORT); } + /** + * Constructor for tests only. + * + * @param scheduler the {@link ScheduledExecutorService} to use during testing. + */ + TPLinkSmartHomeDiscoveryService(ScheduledExecutorService scheduler) throws UnknownHostException { + super(scheduler, SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT_SECONDS, true, null, null); + final InetAddress broadcast = InetAddress.getByName(BROADCAST_IP); + final byte[] discoverbuffer = CryptUtil.encrypt(Commands.getSysinfo()); + discoverPacket = new DatagramPacket(discoverbuffer, discoverbuffer.length, broadcast, + Connection.TP_LINK_SMART_HOME_PORT); + } + @Override public @Nullable String getLastKnownIpAddress(String deviceId) { return idInetAddressCache.get(deviceId); diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeDiscoveryServiceTest.java b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeDiscoveryServiceTest.java index a6e802357da18..e2754dd10006b 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeDiscoveryServiceTest.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeDiscoveryServiceTest.java @@ -35,6 +35,7 @@ import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil; import org.openhab.core.config.discovery.DiscoveryListener; import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.util.SameThreadExecutorService; /** * Test class for {@link TPLinkSmartHomeDiscoveryService} class. @@ -57,7 +58,7 @@ public static List data() { } public void setUp(String filename) throws IOException { - discoveryService = new TPLinkSmartHomeDiscoveryService() { + discoveryService = new TPLinkSmartHomeDiscoveryService(new SameThreadExecutorService()) { @Override protected DatagramSocket sendDiscoveryPacket() throws IOException { return discoverSocket; @@ -91,7 +92,7 @@ public void testScan(String filename, int propertiesSize) throws IOException { setUp(filename); discoveryService.startScan(); ArgumentCaptor discoveryResultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class); - verify(discoveryListener, timeout(1000L)).thingDiscovered(any(), discoveryResultCaptor.capture()); + verify(discoveryListener).thingDiscovered(any(), discoveryResultCaptor.capture()); DiscoveryResult discoveryResult = discoveryResultCaptor.getValue(); assertEquals(TPLinkSmartHomeBindingConstants.BINDING_ID, discoveryResult.getBindingId(), "Check if correct binding id found"); From 5cd44eb4d2b4a7b7b715c89c140ca1e93e3bb0ee Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sun, 26 Oct 2025 15:09:34 +0100 Subject: [PATCH 9/9] Fix flaky test in network binding Signed-off-by: Ravi Nadahar --- .../network/internal/PresenceDetectionTest.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java index c38ee49461f87..8aab5692db5ed 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java @@ -21,9 +21,6 @@ import java.io.IOException; import java.time.Duration; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -40,6 +37,7 @@ import org.openhab.binding.network.internal.utils.NetworkUtils.ArpPingUtilEnum; import org.openhab.binding.network.internal.utils.NetworkUtils.IpPingMethodEnum; import org.openhab.binding.network.internal.utils.PingResult; +import org.openhab.core.util.SameThreadExecutorService; /** * Tests cases for {@see PresenceDetectionValue} @@ -55,9 +53,6 @@ public class PresenceDetectionTest { private @NonNullByDefault({}) PresenceDetection asyncSubject; private @Mock @NonNullByDefault({}) Consumer callback; - private @Mock @NonNullByDefault({}) ExecutorService detectionExecutorService; - private @Mock @NonNullByDefault({}) ExecutorService waitForResultExecutorService; - private @Mock @NonNullByDefault({}) ScheduledExecutorService scheduledExecutorService; private @Mock @NonNullByDefault({}) PresenceDetectionListener listener; private @Mock @NonNullByDefault({}) NetworkUtils networkUtils; @@ -81,7 +76,7 @@ public void setUp() { subject.setUseArpPing(true, "arping", ArpPingUtilEnum.IPUTILS_ARPING); subject.setUseIcmpPing(true); - asyncSubject = spy(new PresenceDetection(listener, Duration.ofSeconds(2), Executors.newSingleThreadExecutor())); + asyncSubject = spy(new PresenceDetection(listener, Duration.ofSeconds(2), new SameThreadExecutorService())); asyncSubject.networkUtils = networkUtils; asyncSubject.setHostname("127.0.0.1"); @@ -174,7 +169,6 @@ public void cacheTest() throws InterruptedException, IOException { // Get value will issue a PresenceDetection internally. asyncSubject.getValue(callback); verify(asyncSubject).performPresenceDetection(); - Thread.sleep(200); // give it some time to execute // Callback should be called once with the result (since we use direct executor) verify(callback, times(1)).accept(any()); @@ -182,9 +176,9 @@ public void cacheTest() throws InterruptedException, IOException { asyncSubject.getValue(callback); verify(callback, times(2)).accept(any()); - // Invalidate value, we should not get a new callback immediately again + // Invalidate value, we should get a new callback immediately asyncSubject.cache.invalidateValue(); asyncSubject.getValue(callback); - verify(callback, times(2)).accept(any()); + verify(callback, times(3)).accept(any()); } }