[systeminfo] dynamic channels (#13562)

* Dynamic channels
* Status messages i8n
* Format fix
* Cache process load values
* Restore channel configs
* Fix test
* Stabilize tests
* Fix CpuLoad1-5-15 update
* Fix test bndrun
* String equals cleanup
* Fix potential null pointer in test

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
Mark Herwege
2022-11-04 13:28:27 +01:00
committed by GitHub
parent a4f6159f09
commit cf2a1afd56
12 changed files with 975 additions and 160 deletions

View File

@@ -74,4 +74,6 @@ Fragment-Host: org.openhab.binding.systeminfo
org.openhab.core.io.console;version='[3.4.0,3.4.1)',\
org.openhab.core.test;version='[3.4.0,3.4.1)',\
org.openhab.core.thing;version='[3.4.0,3.4.1)',\
org.openhab.core.thing.xml;version='[3.4.0,3.4.1)'
org.openhab.core.thing.xml;version='[3.4.0,3.4.1)',\
org.mockito.junit-jupiter;version='[4.1.0,4.1.1)',\
com.github.oshi.oshi-core;version='[6.2.2,6.2.3)'

View File

@@ -24,15 +24,21 @@ import java.net.UnknownHostException;
import java.util.Hashtable;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants;
import org.openhab.binding.systeminfo.internal.SysteminfoHandlerFactory;
import org.openhab.binding.systeminfo.internal.SysteminfoThingTypeProvider;
import org.openhab.binding.systeminfo.internal.discovery.SysteminfoDiscoveryService;
import org.openhab.binding.systeminfo.internal.handler.SysteminfoHandler;
import org.openhab.binding.systeminfo.internal.model.DeviceNotFoundException;
import org.openhab.binding.systeminfo.internal.model.OSHISysteminfo;
import org.openhab.binding.systeminfo.internal.model.SysteminfoInterface;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryResult;
@@ -61,6 +67,7 @@ import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.openhab.core.thing.binding.ThingTypeProvider;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.link.ItemChannelLink;
@@ -78,6 +85,8 @@ import org.openhab.core.types.UnDefType;
* but mock data will be used instead, avoiding potential errors from the OS queries.
* @author Wouter Born - Migrate Groovy to Java tests
*/
@NonNullByDefault
@ExtendWith(MockitoExtension.class)
public class SysteminfoOSGiTest extends JavaOSGiTest {
private static final String DEFAULT_TEST_THING_NAME = "work";
private static final String DEFAULT_TEST_ITEM_NAME = "test";
@@ -97,15 +106,13 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
*/
private static final int DEFAULT_TEST_INTERVAL_MEDIUM = 3;
private Thing systemInfoThing;
private SysteminfoHandler systemInfoHandler;
private GenericItem testItem;
private @Nullable Thing systemInfoThing;
private @Nullable GenericItem testItem;
private SysteminfoInterface mockedSystemInfo;
private ManagedThingProvider managedThingProvider;
private ThingRegistry thingRegistry;
private ItemRegistry itemRegistry;
private SysteminfoHandlerFactory systeminfoHandlerFactory;
private @Mock @NonNullByDefault({}) OSHISysteminfo mockedSystemInfo;
private @NonNullByDefault({}) SysteminfoHandlerFactory systeminfoHandlerFactory;
private @NonNullByDefault({}) ThingRegistry thingRegistry;
private @NonNullByDefault({}) ItemRegistry itemRegistry;
@BeforeEach
public void setUp() {
@@ -113,48 +120,75 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
registerService(volatileStorageService);
// Preparing the mock with OS properties, that are used in the initialize method of SysteminfoHandler
mockedSystemInfo = mock(SysteminfoInterface.class);
when(mockedSystemInfo.getCpuLogicalCores()).thenReturn(new DecimalType(2));
when(mockedSystemInfo.getCpuPhysicalCores()).thenReturn(new DecimalType(2));
when(mockedSystemInfo.getOsFamily()).thenReturn(new StringType("Mock OS"));
when(mockedSystemInfo.getOsManufacturer()).thenReturn(new StringType("Mock OS Manufacturer"));
when(mockedSystemInfo.getOsVersion()).thenReturn(new StringType("Mock Os Version"));
// Make this lenient because the assertInvalidThingConfigurationValuesAreHandled test does not require them
lenient().when(mockedSystemInfo.getCpuLogicalCores()).thenReturn(new DecimalType(2));
lenient().when(mockedSystemInfo.getCpuPhysicalCores()).thenReturn(new DecimalType(2));
lenient().when(mockedSystemInfo.getOsFamily()).thenReturn(new StringType("Mock OS"));
lenient().when(mockedSystemInfo.getOsManufacturer()).thenReturn(new StringType("Mock OS Manufacturer"));
lenient().when(mockedSystemInfo.getOsVersion()).thenReturn(new StringType("Mock Os Version"));
// Following mock method returns will make sure the thing does not get recreated with extra channels
lenient().when(mockedSystemInfo.getNetworkIFCount()).thenReturn(1);
lenient().when(mockedSystemInfo.getDisplayCount()).thenReturn(1);
lenient().when(mockedSystemInfo.getFileOSStoreCount()).thenReturn(1);
lenient().when(mockedSystemInfo.getPowerSourceCount()).thenReturn(1);
lenient().when(mockedSystemInfo.getDriveCount()).thenReturn(1);
lenient().when(mockedSystemInfo.getFanCount()).thenReturn(1);
systeminfoHandlerFactory = getService(ThingHandlerFactory.class, SysteminfoHandlerFactory.class);
SysteminfoInterface oshiSystemInfo = getService(SysteminfoInterface.class);
registerService(mockedSystemInfo);
// Unbind oshiSystemInfo service and bind the mock service to make the systeminfobinding tests independent of
// the external OSHI library
if (oshiSystemInfo != null) {
systeminfoHandlerFactory.unbindSystemInfo(oshiSystemInfo);
waitForAssert(() -> {
systeminfoHandlerFactory = getService(ThingHandlerFactory.class, SysteminfoHandlerFactory.class);
assertThat(systeminfoHandlerFactory, is(notNullValue()));
});
if (systeminfoHandlerFactory != null) {
// Unbind oshiSystemInfo service and bind the mock service to make the systeminfo binding tests independent
// of the external OSHI library
SysteminfoInterface oshiSystemInfo = getService(SysteminfoInterface.class);
if (oshiSystemInfo != null) {
systeminfoHandlerFactory.unbindSystemInfo(oshiSystemInfo);
}
systeminfoHandlerFactory.bindSystemInfo(mockedSystemInfo);
}
systeminfoHandlerFactory.bindSystemInfo(mockedSystemInfo);
managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
assertThat(managedThingProvider, is(notNullValue()));
waitForAssert(() -> {
ThingTypeProvider thingTypeProvider = getService(ThingTypeProvider.class,
SysteminfoThingTypeProvider.class);
assertThat(thingTypeProvider, is(notNullValue()));
});
waitForAssert(() -> {
SysteminfoThingTypeProvider systeminfoThingTypeProvider = getService(SysteminfoThingTypeProvider.class);
assertThat(systeminfoThingTypeProvider, is(notNullValue()));
});
thingRegistry = getService(ThingRegistry.class);
assertThat(thingRegistry, is(notNullValue()));
waitForAssert(() -> {
thingRegistry = getService(ThingRegistry.class);
assertThat(thingRegistry, is(notNullValue()));
});
itemRegistry = getService(ItemRegistry.class);
assertThat(itemRegistry, is(notNullValue()));
waitForAssert(() -> {
itemRegistry = getService(ItemRegistry.class);
assertThat(itemRegistry, is(notNullValue()));
});
}
@AfterEach
public void tearDown() {
if (systemInfoThing != null) {
Thing thing = systemInfoThing;
if (thing != null) {
// Remove the systeminfo thing. The handler will be also disposed automatically
Thing removedThing = thingRegistry.forceRemove(systemInfoThing.getUID());
Thing removedThing = thingRegistry.forceRemove(thing.getUID());
assertThat("The systeminfo thing cannot be deleted", removedThing, is(notNullValue()));
waitForAssert(() -> {
ThingHandler systemInfoHandler = thing.getHandler();
assertThat(systemInfoHandler, is(nullValue()));
});
}
waitForAssert(() -> {
ThingHandler systemInfoHandler = systemInfoThing.getHandler();
assertThat(systemInfoHandler, is(nullValue()));
});
if (testItem != null) {
itemRegistry.remove(DEFAULT_TEST_ITEM_NAME);
}
unregisterService(mockedSystemInfo);
}
private void initializeThingWithChannelAndPID(String channelID, String acceptedItemType, int pid) {
@@ -214,18 +248,24 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
.withKind(ChannelKind.STATE).withConfiguration(channelConfig).build();
systemInfoThing = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
Thing thing = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
.withChannel(channel).build();
systemInfoThing = thing;
managedThingProvider.add(systemInfoThing);
ManagedThingProvider managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
assertThat(managedThingProvider, is(notNullValue()));
if (managedThingProvider != null) {
managedThingProvider.add(thing);
}
waitForAssert(() -> {
systemInfoHandler = (SysteminfoHandler) systemInfoThing.getHandler();
assertThat(systemInfoHandler, is(notNullValue()));
SysteminfoHandler handler = (SysteminfoHandler) thing.getHandler();
assertThat(handler, is(notNullValue()));
});
waitForAssert(() -> {
assertThat("Thing is not initilized, before an Item is created", systemInfoThing.getStatus(),
assertThat("Thing is not initialized, before an Item is created", thing.getStatus(),
anyOf(equalTo(ThingStatus.OFFLINE), equalTo(ThingStatus.ONLINE)));
});
@@ -233,11 +273,15 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
}
private void assertItemState(String acceptedItemType, String itemName, String priority, State expectedState) {
Thing thing = systemInfoThing;
if (thing == null) {
throw new AssertionError("Thing is null");
}
waitForAssert(() -> {
ThingStatusDetail thingStatusDetail = systemInfoThing.getStatusInfo().getStatusDetail();
String description = systemInfoThing.getStatusInfo().getDescription();
ThingStatusDetail thingStatusDetail = thing.getStatusInfo().getStatusDetail();
String description = thing.getStatusInfo().getDescription();
assertThat("Thing status detail is " + thingStatusDetail + " with description " + description,
systemInfoThing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
thing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
});
// The binding starts all refresh tasks in SysteminfoHandler.scheduleUpdates() after this delay !
try {
@@ -254,9 +298,9 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
}
int waitTime;
if (priority.equals("High")) {
if ("High".equals(priority)) {
waitTime = DEFAULT_TEST_INTERVAL_HIGH * 1000;
} else if (priority.equals("Medium")) {
} else if ("Medium".equals(priority)) {
waitTime = DEFAULT_TEST_INTERVAL_MEDIUM * 1000;
} else {
waitTime = 100;
@@ -269,16 +313,25 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
}
private void intializeItem(ChannelUID channelUID, String itemName, String acceptedItemType) {
if (acceptedItemType.equals("Number")) {
testItem = new NumberItem(itemName);
} else if (acceptedItemType.equals("String")) {
testItem = new StringItem(itemName);
GenericItem item = null;
if ("Number".equals(acceptedItemType)) {
item = new NumberItem(itemName);
} else if ("String".equals(acceptedItemType)) {
item = new StringItem(itemName);
}
itemRegistry.add(testItem);
if (item == null) {
throw new AssertionError("Item is null");
}
itemRegistry.add(item);
testItem = item;
ManagedItemChannelLinkProvider itemChannelLinkProvider = getService(ManagedItemChannelLinkProvider.class);
assertThat(itemChannelLinkProvider, is(notNullValue()));
if (itemChannelLinkProvider == null) {
return;
}
itemChannelLinkProvider.add(new ItemChannelLink(itemName, channelUID));
}
@@ -300,29 +353,13 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
private void testInvalidConfiguration() {
waitForAssert(() -> {
assertThat("Invalid configuratuin is used !", systemInfoThing.getStatus(),
is(equalTo(ThingStatus.OFFLINE)));
assertThat(systemInfoThing.getStatusInfo().getStatusDetail(),
is(equalTo(ThingStatusDetail.HANDLER_INITIALIZING_ERROR)));
assertThat(systemInfoThing.getStatusInfo().getDescription(), is(equalTo("Thing cannot be initialized!")));
});
}
@Test
public void assertThingStatusIsUninitializedWhenThereIsNoSysteminfoServiceProvided() {
// Unbind the mock service to verify the systeminfo thing will not be initialized when no systeminfo service is
// provided
systeminfoHandlerFactory.unbindSystemInfo(mockedSystemInfo);
ThingTypeUID thingTypeUID = SysteminfoBindingConstants.THING_TYPE_COMPUTER;
ThingUID thingUID = new ThingUID(thingTypeUID, DEFAULT_TEST_THING_NAME);
systemInfoThing = ThingBuilder.create(thingTypeUID, thingUID).build();
managedThingProvider.add(systemInfoThing);
waitForAssert(() -> {
assertThat("The thing status is uninitialized when systeminfo service is missing",
systemInfoThing.getStatus(), equalTo(ThingStatus.UNINITIALIZED));
Thing thing = systemInfoThing;
if (thing != null) {
assertThat("Invalid configuration is used !", thing.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
assertThat(thing.getStatusInfo().getStatusDetail(),
is(equalTo(ThingStatusDetail.HANDLER_INITIALIZING_ERROR)));
assertThat(thing.getStatusInfo().getDescription(), is(equalTo("@text/offline.cannot-initialize")));
}
});
}
@@ -672,7 +709,7 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
mockedDriveSerialNumber);
}
@Disabled
// Re-enable this previously disabled test, as it is not relying on hardware anymore, but a mocked object
// There is a bug opened for this issue - https://github.com/dblock/oshi/issues/185
@Test
public void assertChannelSensorsCpuTempIsUpdated() {
@@ -874,7 +911,7 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
@Override
protected String getHostName() throws UnknownHostException {
if (hostname.equals("unresolved")) {
if ("unresolved".equals(hostname)) {
throw new UnknownHostException();
}
return hostname;
@@ -938,6 +975,10 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
Inbox inbox = getService(Inbox.class);
assertThat(inbox, is(notNullValue()));
if (inbox == null) {
return;
}
waitForAssert(() -> {
List<DiscoveryResult> results = inbox.stream().filter(InboxPredicates.forThingUID(computerUID))
.collect(toList());
@@ -951,8 +992,13 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
assertThat(systemInfoThing, is(notNullValue()));
});
Thing thing = systemInfoThing;
if (thing == null) {
return;
}
waitForAssert(() -> {
assertThat("Thing is not initialized.", systemInfoThing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
assertThat("Thing is not initialized.", thing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
});
}
@@ -1020,7 +1066,7 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
// The pid of the System idle process in Windows
int pid = 0;
PercentType mockedProcessLoad = new PercentType(3);
DecimalType mockedProcessLoad = new DecimalType(3);
when(mockedSystemInfo.getProcessCpuUsage(pid)).thenReturn(mockedProcessLoad);
initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
@@ -1037,15 +1083,27 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
String acceptedItemType = "Number";
initializeThingWithChannel(DEFAULT_TEST_CHANNEL_ID, acceptedItemType);
Channel channel = systemInfoThing.getChannel(DEFAULT_TEST_CHANNEL_ID);
Thing thing = systemInfoThing;
if (thing == null) {
throw new AssertionError("Thing is null");
}
Channel channel = thing.getChannel(DEFAULT_TEST_CHANNEL_ID);
if (channel == null) {
throw new AssertionError("Channel '" + DEFAULT_TEST_CHANNEL_ID + "' is null");
}
ThingHandler thingHandler = thing.getHandler();
if (thingHandler == null) {
throw new AssertionError("Thing handler is null");
}
if (!(thingHandler.getClass().equals(SysteminfoHandler.class))) {
throw new AssertionError("Thing handler not of class SysteminfoHandler");
}
SysteminfoHandler handler = (SysteminfoHandler) thingHandler;
waitForAssert(() -> {
assertThat("The initial priority of channel " + channel.getUID() + " is not as expected.",
channel.getConfiguration().get(priorityKey), is(equalTo(initialPriority)));
assertThat(systemInfoHandler.getHighPriorityChannels().contains(channel.getUID()), is(true));
assertThat(handler.getHighPriorityChannels().contains(channel.getUID()), is(true));
});
// Change the priority of a channel, keep the pid
@@ -1056,15 +1114,15 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
.withType(channel.getChannelTypeUID()).withKind(channel.getKind()).withConfiguration(updatedConfig)
.build();
Thing updatedThing = ThingBuilder.create(systemInfoThing.getThingTypeUID(), systemInfoThing.getUID())
.withConfiguration(systemInfoThing.getConfiguration()).withChannel(updatedChannel).build();
Thing updatedThing = ThingBuilder.create(thing.getThingTypeUID(), thing.getUID())
.withConfiguration(thing.getConfiguration()).withChannel(updatedChannel).build();
systemInfoHandler.thingUpdated(updatedThing);
handler.thingUpdated(updatedThing);
waitForAssert(() -> {
assertThat("The prority of the channel was not updated: ", channel.getConfiguration().get(priorityKey),
is(equalTo(newPriority)));
assertThat(systemInfoHandler.getLowPriorityChannels().contains(channel.getUID()), is(true));
assertThat(handler.getLowPriorityChannels().contains(channel.getUID()), is(true));
});
}
}