/**
* Copyright (c) 2010-2017 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.modbus.internal;
import static org.mockito.Mockito.*;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.openhab.binding.modbus.ModbusBindingProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.model.item.binding.BindingConfigParseException;
import org.osgi.service.cm.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.wimpi.modbus.procimg.SimpleDigitalIn;
/**
* Testing how configuration update is handled
*
*/
@RunWith(Parameterized.class)
public class ConfigUpdatedTestCase extends TestCaseSupport {
@Parameters
public static List<Object[]> data() {
List<Object[]> parameters = new ArrayList<>();
for (ServerType server : TEST_SERVERS) {
parameters.add(new Object[] { server });
}
return parameters;
}
@SuppressWarnings("serial")
public static class ExpectedFailure extends AssertionError {
public ExpectedFailure(Throwable cause) {
initCause(cause);
}
}
public ConfigUpdatedTestCase(ServerType serverType) {
super();
this.serverType = serverType;
// Server is a bit slower to respond than normally
// this for the testConfigUpdatedWhilePolling
this.artificialServerWait = 1000; // Also remember default timeout for tcp Modbus.DEFAULT_TIMEOUT
}
@Test
public void testConfigUpdated() throws UnknownHostException, ConfigurationException, BindingConfigParseException,
org.osgi.service.cm.ConfigurationException {
// Modbus server ("modbus slave") has two digital inputs
spi.addDigitalIn(new SimpleDigitalIn(true));
spi.addDigitalIn(new SimpleDigitalIn(false));
binding = new ModbusBinding();
// simulate configuration changes
for (int i = 0; i < 2; i++) {
binding.updated(
addSlave(newLongPollBindingConfig(), SLAVE_NAME, ModbusBindingProvider.TYPE_DISCRETE, null, 0, 2));
}
configureSwitchItemBinding(2, SLAVE_NAME, 0);
binding.execute();
// Give the system some time to make the expected connections & requests
waitForRequests(1);
if (!serverType.equals(ServerType.UDP)) {
waitForConnectionsReceived(1);
}
verifyEvents();
}
private void verifyEvents() throws ExpectedFailure {
verify(eventPublisher, never()).postCommand(null, null);
verify(eventPublisher, never()).sendCommand(null, null);
try {
verify(eventPublisher).postUpdate("Item1", OnOffType.ON);
verify(eventPublisher).postUpdate("Item2", OnOffType.OFF);
} catch (AssertionError e) {
throw new ExpectedFailure(e);
}
verifyNoMoreInteractions(eventPublisher);
}
/**
* To verify fix for https://github.com/openhab/openhab1-addons/issues/5078
*
* @throws UnknownHostException
* @throws org.osgi.service.cm.ConfigurationException
* @throws BindingConfigParseException
* @throws InterruptedException
*/
@Test
public void testConfigUpdatedWhilePolling() throws UnknownHostException, org.osgi.service.cm.ConfigurationException,
BindingConfigParseException, InterruptedException {
final Logger logger = LoggerFactory.getLogger(ConfigUpdatedTestCase.class);
// run this test only for tcp server due to the customized connection string
Assume.assumeTrue(serverType.equals(ServerType.TCP));
MAX_WAIT_REQUESTS_MILLIS = 10000;
spi.addDigitalIn(new SimpleDigitalIn(true));
spi.addDigitalIn(new SimpleDigitalIn(false));
binding = new ModbusBinding();
// Customized connection settings, keep the connection open for 2000s, no connection retries, 300ms connection
// timeout
String connection = String.format("%s:%d:30:2000000:0:1:300", localAddress().getHostAddress(), tcpModbusPort);
Dictionary<String, Object> cfg = addSlave(newLongPollBindingConfig(), serverType, connection, SLAVE_NAME,
ModbusBindingProvider.TYPE_DISCRETE, null, 1, 0, 2);
putSlaveConfigParameter(cfg, serverType, SLAVE_NAME, "updateunchangeditems", "true");
binding.updated(cfg);
configureSwitchItemBinding(2, SLAVE_NAME, 0);
Thread executeOnBackground = new Thread(new Runnable() {
@Override
public void run() {
logger.info("First execution started");
binding.execute();
logger.info("First execution finished");
}
});
executeOnBackground.start();
Thread.sleep(100);
// Connection should be now open (since ~100ms passed since connection)
// But the first query is still on the way (since server is so slow)
// Simulate config update
// any returned connections to the old connection pool will be closed immediately on return
binding.updated(cfg);
// Let the previous polling round end before entering the next round
// We are not really interested what would happen in the "transient period"
// where the old connections are ongoing at the same time as the new ones
executeOnBackground.join();
// Polling should work after config update
logger.info("Second execution started");
binding.execute();
logger.info("Second execution finished");
// three requests, two of those due to execute() commands in this test,
// one due to initial automatic execute() (when updated the first time)
waitForRequests(3);
// two connections, connection is closed on second updated(), and thus connection needs to re-initated
waitForConnectionsReceived(2);
verify(eventPublisher, times(3)).postUpdate("Item1", OnOffType.ON);
verify(eventPublisher, times(3)).postUpdate("Item2", OnOffType.OFF);
verifyNoMoreInteractions(eventPublisher);
}
}