gt:         DecimalType mockedCpuThreadsValue = new DecimalType(16);
context: /**
   * Copyright (c) 2010-2019 Contributors to the openHAB project
   *
   * See the NOTICE file(s) distributed with this work for additional
   * information.
   *
   * This program and the accompanying materials are made available under the
   * terms of the Eclipse Public License 2.0 which is available at
   * http://www.eclipse.org/legal/epl-2.0
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  package org.openhab.binding.systeminfo.test;
  
  import static java.lang.Thread.sleep;
  import static java.util.stream.Collectors.toList;
  import static org.hamcrest.CoreMatchers.*;
  import static org.junit.Assert.*;
  import static org.mockito.Mockito.*;
  
  import java.math.BigDecimal;
  import java.net.UnknownHostException;
  import java.util.Hashtable;
  import java.util.List;
  
  import org.eclipse.smarthome.config.core.Configuration;
  import org.eclipse.smarthome.config.discovery.DiscoveryResult;
  import org.eclipse.smarthome.config.discovery.DiscoveryService;
  import org.eclipse.smarthome.config.discovery.inbox.Inbox;
  import org.eclipse.smarthome.config.discovery.inbox.InboxPredicates;
  import org.eclipse.smarthome.core.items.GenericItem;
  import org.eclipse.smarthome.core.items.ItemNotFoundException;
  import org.eclipse.smarthome.core.items.ItemRegistry;
  import org.eclipse.smarthome.core.library.items.NumberItem;
  import org.eclipse.smarthome.core.library.items.StringItem;
  import org.eclipse.smarthome.core.library.types.DecimalType;
  import org.eclipse.smarthome.core.library.types.StringType;
  import org.eclipse.smarthome.core.thing.Channel;
  import org.eclipse.smarthome.core.thing.ChannelUID;
  import org.eclipse.smarthome.core.thing.ManagedThingProvider;
  import org.eclipse.smarthome.core.thing.Thing;
  import org.eclipse.smarthome.core.thing.ThingProvider;
  import org.eclipse.smarthome.core.thing.ThingRegistry;
  import org.eclipse.smarthome.core.thing.ThingStatus;
  import org.eclipse.smarthome.core.thing.ThingStatusDetail;
  import org.eclipse.smarthome.core.thing.ThingTypeUID;
  import org.eclipse.smarthome.core.thing.ThingUID;
  import org.eclipse.smarthome.core.thing.binding.ThingHandler;
  import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
  import org.eclipse.smarthome.core.thing.binding.builder.ChannelBuilder;
  import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder;
  import org.eclipse.smarthome.core.thing.link.ItemChannelLink;
  import org.eclipse.smarthome.core.thing.link.ManagedItemChannelLinkProvider;
  import org.eclipse.smarthome.core.thing.type.ChannelKind;
  import org.eclipse.smarthome.core.thing.type.ChannelTypeUID;
  import org.eclipse.smarthome.core.types.State;
  import org.eclipse.smarthome.core.types.UnDefType;
  import org.eclipse.smarthome.test.java.JavaOSGiTest;
  import org.eclipse.smarthome.test.storage.VolatileStorageService;
  import org.junit.After;
  import org.junit.Before;
  import org.junit.Ignore;
  import org.junit.Test;
  import org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants;
  import org.openhab.binding.systeminfo.internal.SysteminfoHandlerFactory;
  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.SysteminfoInterface;
  
  /**
   * OSGi tests for the {@link SysteminfoHandler}
   *
   * @author Svilen Valkanov - Initial contribution
   * @author Lyubomir Papazov - Created a mock systeminfo object. This way, access to the user's OS will not be required,
   *         but mock data will be used instead, avoiding potential errors from the OS queries.
   * @author Wouter Born - Migrate Groovy to Java tests
   */
  public class SysteminfoOSGiTest extends JavaOSGiTest {
      private static final String DEFAULT_TEST_THING_NAME = "work";
      private static final String DEFAULT_TEST_ITEM_NAME = "test";
      private static final String DEFAULT_CHANNEL_TEST_PRIORITY = "High";
      private static final int DEFAULT_CHANNEL_PID = -1;
      private static final String DEFAULT_TEST_CHANNEL_ID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD;
      private static final int DEFAULT_DEVICE_INDEX = 0;
  
      /**
       * Refresh time in seconds for tasks with priority High.
       * Default value for the parameter interval_high in the thing configuration
       */
      private static final int DEFAULT_TEST_INTERVAL_HIGH = 1;
  
      /**
       * Refresh time in seconds for tasks with priority Medium.
       */
      private static final int DEFAULT_TEST_INTERVAL_MEDIUM = 3;
  
      private Thing systemInfoThing;
      private SysteminfoHandler systemInfoHandler;
      private GenericItem testItem;
  
      private SysteminfoInterface mockedSystemInfo;
      private ManagedThingProvider managedThingProvider;
      private ThingRegistry thingRegistry;
      private ItemRegistry itemRegistry;
      private SysteminfoHandlerFactory systeminfoHandlerFactory;
  
      @Before
      public void setUp() {
  
          VolatileStorageService volatileStorageService = new VolatileStorageService();
          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"));
  
          systeminfoHandlerFactory = getService(ThingHandlerFactory.class, SysteminfoHandlerFactory.class);
          SysteminfoInterface oshiSystemInfo = getService(SysteminfoInterface.class);
  
          // 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);
          }
          systeminfoHandlerFactory.bindSystemInfo(mockedSystemInfo);
  
          managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
          assertThat(managedThingProvider, is(notNullValue()));
  
          thingRegistry = getService(ThingRegistry.class);
          assertThat(thingRegistry, is(notNullValue()));
  
          itemRegistry = getService(ItemRegistry.class);
          assertThat(itemRegistry, is(notNullValue()));
      }
  
      @After
      public void tearDown() {
          if (systemInfoThing != null) {
              // Remove the systeminfo thing. The handler will be also disposed automatically
              Thing removedThing = thingRegistry.forceRemove(systemInfoThing.getUID());
              assertThat("The systeminfo thing cannot be deleted", removedThing, is(notNullValue()));
          }
          waitForAssert(() -> {
              ThingHandler systemInfoHandler = systemInfoThing.getHandler();
              assertThat(systemInfoHandler, is(nullValue()));
          });
  
          if (testItem != null) {
              itemRegistry.remove(DEFAULT_TEST_ITEM_NAME);
          }
      }
  
      private void initializeThingWithChannelAndPID(String channelID, String acceptedItemType, int pid) {
          Configuration thingConfig = new Configuration();
          thingConfig.put(SysteminfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
                  new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
          thingConfig.put(SysteminfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
                  new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
          String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
  
          initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
      }
  
      private void initializeThingWithChannelAndPriority(String channelID, String acceptedItemType, String priority) {
          Configuration thingConfig = new Configuration();
          thingConfig.put(SysteminfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
                  new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
          thingConfig.put(SysteminfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
                  new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
          int pid = DEFAULT_CHANNEL_PID;
  
          initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
      }
  
      private void initializeThingWithConfiguration(Configuration config) {
          String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
          String channelID = DEFAULT_TEST_CHANNEL_ID;
          String acceptedItemType = "String";
          int pid = DEFAULT_CHANNEL_PID;
  
          initializeThing(config, channelID, acceptedItemType, priority, pid);
      }
  
      private void initializeThingWithChannel(String channelID, String acceptedItemType) {
          Configuration thingConfig = new Configuration();
          thingConfig.put(SysteminfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
                  new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
          thingConfig.put(SysteminfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
                  new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
  
          String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
          int pid = DEFAULT_CHANNEL_PID;
          initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
      }
  
      private void initializeThing(Configuration thingConfiguration, String channelID, String acceptedItemType,
              String priority, int pid) {
          ThingTypeUID thingTypeUID = SysteminfoBindingConstants.THING_TYPE_COMPUTER;
          ThingUID thingUID = new ThingUID(thingTypeUID, DEFAULT_TEST_THING_NAME);
  
          ChannelUID channelUID = new ChannelUID(thingUID, channelID);
          ChannelTypeUID channelTypeUID = new ChannelTypeUID(SysteminfoBindingConstants.BINDING_ID,
                  channelUID.getIdWithoutGroup());
          Configuration channelConfig = new Configuration();
          channelConfig.put("priority", priority);
          channelConfig.put("pid", new BigDecimal(pid));
          Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
                  .withKind(ChannelKind.STATE).withConfiguration(channelConfig).build();
  
          systemInfoThing = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
                  .withChannel(channel).build();
  
          managedThingProvider.add(systemInfoThing);
  
          waitForAssert(() -> {
              systemInfoHandler = (SysteminfoHandler) systemInfoThing.getHandler();
              assertThat(systemInfoHandler, is(notNullValue()));
          });
  
          waitForAssert(() -> {
              assertThat("Thing is not initilized, before an Item is created", systemInfoThing.getStatus(),
                      anyOf(equalTo(ThingStatus.OFFLINE), equalTo(ThingStatus.ONLINE)));
          });
  
          intializeItem(channelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
      }
  
      private void assertItemState(String acceptedItemType, String itemName, String priority, State expectedState) {
          waitForAssert(() -> {
              ThingStatusDetail thingStatusDetail = systemInfoThing.getStatusInfo().getStatusDetail();
              String description = systemInfoThing.getStatusInfo().getDescription();
              assertThat("Thing status detail is " + thingStatusDetail + " with description " + description,
                      systemInfoThing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
          });
          // The binding starts all refresh tasks in SysteminfoHandler.scheduleUpdates() after this delay !
          try {
              sleep(SysteminfoHandler.WAIT_TIME_CHANNEL_ITEM_LINK_INIT * 1000);
          } catch (InterruptedException e) {
              throw new AssertionError("Interrupted while sleeping");
          }
  
          GenericItem item;
          try {
              item = (GenericItem) itemRegistry.getItem(itemName);
          } catch (ItemNotFoundException e) {
              throw new AssertionError("Item not found in registry");
          }
  
          int waitTime;
          if (priority.equals("High")) {
              waitTime = DEFAULT_TEST_INTERVAL_HIGH * 1000;
          } else if (priority.equals("Medium")) {
              waitTime = DEFAULT_TEST_INTERVAL_MEDIUM * 1000;
          } else {
              waitTime = 100;
          }
  
          waitForAssert(() -> {
              State itemState = item.getState();
              assertThat(itemState, is(equalTo(expectedState)));
          }, waitTime, DFL_SLEEP_TIME);
  
      }
  
      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);
          }
          itemRegistry.add(testItem);
  
          ManagedItemChannelLinkProvider itemChannelLinkProvider = getService(ManagedItemChannelLinkProvider.class);
          assertThat(itemChannelLinkProvider, is(notNullValue()));
  
          itemChannelLinkProvider.add(new ItemChannelLink(itemName, channelUID));
      }
  
      @Test
      public void assertInvalidThingConfigurationValuesAreHandled() {
          Configuration configuration = new Configuration();
  
          // invalid value - must be positive
          int refreshIntervalHigh = -5;
          configuration.put(SysteminfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME, new BigDecimal(refreshIntervalHigh));
  
          int refreshIntervalMedium = 3;
          configuration.put(SysteminfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
                  new BigDecimal(refreshIntervalMedium));
          initializeThingWithConfiguration(configuration);
  
          testInvalidConfiguration();
      }
  
      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));
          });
      }
  
      @Test
      public void assertMediumPriorityChannelIsUpdated() {
          String channnelID = DEFAULT_TEST_CHANNEL_ID;
          String acceptedItemType = "Number";
          String priority = "Medium";
  
          initializeThingWithChannelAndPriority(channnelID, acceptedItemType, priority);
          assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, priority, UnDefType.UNDEF);
      }
  
      @Test
      public void assertStateOfSecondDeviceIsUpdated() {
          // This test assumes that at least 2 network interfaces are present on the test platform
          int deviceIndex = 1;
          String channnelID = "network" + deviceIndex + "#mac";
          String acceptedItemType = "String";
  
          initializeThingWithChannel(channnelID, acceptedItemType);
          assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, UnDefType.UNDEF);
      }
  
      @Test
      public void assertChannelCpuLoad1IsUpdated() {
          String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD_1;
          String acceptedItemType = "Number";
  
          DecimalType mockedCpuLoad1Value = new DecimalType(1.1);
          when(mockedSystemInfo.getCpuLoad1()).thenReturn(mockedCpuLoad1Value);
  
          initializeThingWithChannel(channnelID, acceptedItemType);
          assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad1Value);
      }
  
      @Test
      public void assertChannelCpuLoad5IsUpdated() {
          String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD_5;
          String acceptedItemType = "Number";
  
          DecimalType mockedCpuLoad5Value = new DecimalType(5.5);
          when(mockedSystemInfo.getCpuLoad5()).thenReturn(mockedCpuLoad5Value);
  
          initializeThingWithChannel(channnelID, acceptedItemType);
          assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad5Value);
      }
  
      @Test
      public void assertChannelCpuLoad15IsUpdated() {
          String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD_15;
          String acceptedItemType = "Number";
  
          DecimalType mockedCpuLoad15Value = new DecimalType(15.15);
          when(mockedSystemInfo.getCpuLoad15()).thenReturn(mockedCpuLoad15Value);
  
          initializeThingWithChannel(channnelID, acceptedItemType);
          assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad15Value);
      }
  
      @Test
      public void assertChannelCpuThreadsIsUpdated() {
          String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_THREADS;
          String acceptedItemType = "Number";
  
  