Files
JIMRI/java/test/jmri/jmrix/openlcb/OlcbSensorTest.java
T
2026-06-17 14:00:51 +02:00

435 lines
16 KiB
Java

package jmri.jmrix.openlcb;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.regex.Pattern;
import org.junit.Assert;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.openlcb.EventID;
import org.openlcb.implementations.EventTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jmri.JmriException;
import jmri.Sensor;
import jmri.jmrix.can.CanMessage;
import jmri.util.junit.annotations.ToDo;
import jmri.util.JUnitUtil;
import jmri.util.NamedBeanComparator;
import jmri.util.PropertyChangeListenerScaffold;
import jmri.util.ThreadingUtil;
/**
* Tests for the jmri.jmrix.openlcb.OlcbSensor class.
* These tests are run separately because they leave a lot of threads behind.
* @author Bob Jacobsen Copyright 2008, 2010
*/
@DisabledIfSystemProperty(named ="jmri.skipTestsRequiringSeparateRunning", matches ="true")
public class OlcbSensorTest extends jmri.implementation.AbstractSensorTestBase {
private static final Logger log = LoggerFactory.getLogger(OlcbSensorTest.class);
protected PropertyChangeListenerScaffold l;
@Override
public int numListeners() {
return 0;
}
@Override
public void checkActiveMsgSent() {
Assert.assertTrue(new OlcbAddress("1.2.3.4.5.6.7.8", null).match(ti.tc.rcvMessage));
}
@Override
public void checkInactiveMsgSent() {
Assert.assertTrue(new OlcbAddress("1.2.3.4.5.6.7.9", null).match(ti.tc.rcvMessage));
}
@Disabled("Test requires further setup")
@ToDo("Check checkActiveMsgSent() producing correct result")
@Test
@Override
public void testCommandSentActive() {}
@Disabled("Test requires further setup")
@ToDo("Check checkInactiveMsgSent() producing correct result")
@Test
@Override
public void testCommandSentInactive() {}
@Override
public void checkStatusRequestMsgSent() {
ti.flush();
ti.assertSentMessage(":X198F4C4CN0102030405060708;");
}
@Test
public void testIncomingChange() {
// message for Active and Inactive
CanMessage mActive = new CanMessage(
new int[]{1, 2, 3, 4, 5, 6, 7, 8},
0x195B4123
);
mActive.setExtended(true);
CanMessage mInactive = new CanMessage(
new int[]{1, 2, 3, 4, 5, 6, 7, 9},
0x195B4123
);
mInactive.setExtended(true);
// check states
Assert.assertEquals(Sensor.UNKNOWN, t.getKnownState());
ti.sendMessage(mActive);
Assert.assertEquals(Sensor.ACTIVE,t.getKnownState());
ti.sendMessage(mInactive);
Assert.assertEquals(Sensor.INACTIVE, t.getKnownState());
}
@Test
public void testRecoverUponStartup() {
ti.flush();
Assert.assertNotNull(ti.tc.rcvMessage);
log.debug("recv msg: {} header {}", ti.tc.rcvMessage, Integer.toHexString(ti.tc.rcvMessage.getHeader()));
CanMessage expected = new CanMessage(new byte[]{1,2,3,4,5,6,7,8}, 0x198F4C4C);
expected.setExtended(true);
Assert.assertEquals(expected, ti.tc.rcvMessage);
// message for Active and Inactive
CanMessage mStateActive = new CanMessage(
new int[]{1, 2, 3, 4, 5, 6, 7, 8},
0x194C4123
);
mStateActive.setExtended(true);
CanMessage mStateInactive = new CanMessage(
new int[]{1, 2, 3, 4, 5, 6, 7, 9},
0x194C4123
);
mStateInactive.setExtended(true);
// check states
Assert.assertEquals(Sensor.UNKNOWN, t.getKnownState());
ti.sendMessage(mStateActive);
Assert.assertEquals(Sensor.ACTIVE,t.getKnownState());
ti.sendMessage(mStateInactive);
Assert.assertEquals(Sensor.INACTIVE, t.getKnownState());
}
@Test
public void testMomentarySensor() {
OlcbSensor s = new OlcbSensor("M", "1.2.3.4.5.6.7.8", ti.systemConnectionMemo);
s.finishLoad();
// message for Active and Inactive
CanMessage mActive = new CanMessage(
new int[]{1, 2, 3, 4, 5, 6, 7, 8},
0x195B4123
);
mActive.setExtended(true);
// check states
Assert.assertEquals(Sensor.UNKNOWN, s.getKnownState());
ti.sendMessage(mActive);
Assert.assertEquals(Sensor.ACTIVE,s.getKnownState());
JUnitUtil.waitFor( ()-> (s.getKnownState() != Sensor.ACTIVE),"wait sensor not active");
Assert.assertEquals(Sensor.INACTIVE, s.getKnownState());
// local flip
s.setKnownState(Sensor.ACTIVE);
Assert.assertEquals(Sensor.ACTIVE,s.getKnownState());
JUnitUtil.waitFor( ()-> (s.getKnownState() != Sensor.ACTIVE),"wait sensor not active local flip");
Assert.assertEquals(Sensor.INACTIVE, s.getKnownState());
}
@Test
public void testLocalChange() throws JmriException {
ti.tc.rcvMessage = null;
t.setKnownState(Sensor.ACTIVE);
Assert.assertEquals(Sensor.ACTIVE, t.getKnownState());
ti.flush();
Assert.assertNotNull(ti.tc.rcvMessage);
log.debug("recv msg: {} header {}", ti.tc.rcvMessage, Integer.toHexString(ti.tc.rcvMessage.getHeader()));
checkActiveMsgSent();
ti.tc.rcvMessage = null;
t.setKnownState(Sensor.INACTIVE);
Assert.assertEquals(Sensor.INACTIVE, t.getKnownState());
ti.flush();
checkInactiveMsgSent();
// Repeat send
ti.tc.rcvMessage = null;
t.setKnownState(Sensor.INACTIVE);
Assert.assertEquals(Sensor.INACTIVE, t.getKnownState());
ti.flush();
checkInactiveMsgSent();
}
@Test
public void testAuthoritative() throws JmriException {
t.setState(Sensor.ACTIVE);
ti.flush();
// message for Active and Inactive
CanMessage qActive = new CanMessage(new int[]{1, 2, 3, 4, 5, 6, 7, 8},
0x19914123
);
qActive.setExtended(true);
ti.sendMessage(qActive);
ti.flush();
CanMessage expected = new CanMessage(new int[]{1, 2, 3, 4, 5, 6, 7, 8},
0x19544c4c);
expected.setExtended(true);
Assert.assertEquals(expected, ti.tc.rcvMessage);
ti.tc.rcvMessage = null;
((OlcbSensor) t).setAuthoritative(false);
t.setState(Sensor.INACTIVE);
ti.flush();
ti.sendMessage(qActive);
ti.flush();
expected = new CanMessage(new int[]{1, 2, 3, 4, 5, 6, 7, 8},
0x19547c4c);
expected.setExtended(true);
Assert.assertEquals(expected, ti.tc.rcvMessage);
}
@Test
public void testForgetState() throws JmriException {
t.dispose(); // dispose of the existing sensor.
OlcbSensor s = new OlcbSensor("M", "1.2.3.4.5.6.7.8;1.2.3.4.5.6.7.9", ti.systemConnectionMemo);
s.setProperty(OlcbUtils.PROPERTY_LISTEN, Boolean.FALSE.toString());
s.finishLoad();
t = s; // give t a value so the test teardown functions.
ti.flush();
ti.tc.rcvMessage = null;
ti.sendMessageAndExpectResponse(":X19914123N0102030405060708;",
":X19547C4CN0102030405060708;");
s.setKnownState(Sensor.ACTIVE);
ti.flush();
Assert.assertEquals(Sensor.ACTIVE, s.getKnownState());
ti.assertSentMessage(":X195B4c4cN0102030405060708;");
s.addPropertyChangeListener(l);
ti.sendMessageAndExpectResponse(":X19914123N0102030405060708;",
":X19544C4CN0102030405060708;");
// Getting a state notify will not change state now.
ti.sendMessage(":X19544123N0102030405060709;");
Assert.assertEquals("no call", 0, l.getCallCount());
l.resetPropertyChanged();
Assert.assertEquals(Sensor.ACTIVE, s.getKnownState());
// Resets the turnout to unknown state
s.setState(Sensor.UNKNOWN);
JUnitUtil.waitFor( () -> l.getPropertyChanged(),"listener property changed");
Assert.assertEquals("called once",1,l.getCallCount());
l.resetPropertyChanged();
ti.assertNoSentMessages();
// state is reported as unknown to the bus
ti.sendMessageAndExpectResponse(":X19914123N0102030405060708;",
":X19547C4CN0102030405060708;");
// getting a state notify will change state
ti.sendMessage(":X19544123N0102030405060709;");
JUnitUtil.waitFor( () -> l.getPropertyChanged(),"listener property changed");
Assert.assertEquals("called once",1,l.getCallCount());
l.resetPropertyChanged();
Assert.assertEquals(Sensor.INACTIVE, s.getKnownState());
// state is reported as known (thrown==invalid)
ti.sendMessageAndExpectResponse(":X19914123N0102030405060708;",
":X19545C4CN0102030405060708;");
// getting a state notify will not change state
ti.sendMessage(":X19544123N0102030405060708;");
Assert.assertEquals("no call", 0, l.getCallCount());
l.resetPropertyChanged();
Assert.assertEquals(Sensor.INACTIVE, s.getKnownState());
}
@Test
public void testQueryState() throws JmriException {
OlcbSensor s = (OlcbSensor) t;
ti.tc.rcvMessage = null;
Assert.assertEquals(Sensor.UNKNOWN, s.getState());
// Default sensors listen to identified messages at all times.
ti.sendMessage(":X19544123N0102030405060708;");
Assert.assertEquals(Sensor.ACTIVE, s.getState());
ti.sendMessage(":X19544123N0102030405060709;");
Assert.assertEquals(Sensor.INACTIVE, t.getState());
ti.tc.rcvMessage = null;
t.requestUpdateFromLayout();
ti.assertSentMessage(":X198F4C4CN0102030405060708;");
ti.sendMessage(":X19544123N0102030405060708;");
Assert.assertEquals(Sensor.ACTIVE, t.getState());
// Actual sequence from sensor table data model
t.setKnownState(Sensor.UNKNOWN);
ti.tc.rcvMessage = null;
t.requestUpdateFromLayout();
ti.assertSentMessage(":X198F4C4CN0102030405060708;");
Assert.assertEquals(Sensor.UNKNOWN, t.getState());
ti.sendMessage(":X19544123N0102030405060709;");
Assert.assertEquals(Sensor.INACTIVE, t.getState());
}
@Test
public void testQueryStateNotAlwaysListen() throws JmriException {
OlcbSensor s = (OlcbSensor) t;
s.setListeningToStateMessages(false);
ti.flush();
ti.tc.rcvMessage = null;
Assert.assertEquals(Sensor.UNKNOWN, s.getState());
ti.sendMessage(":X19544123N0102030405060708;");
Assert.assertEquals(Sensor.ACTIVE, s.getState());
ti.sendMessage(":X19544123N0102030405060709;");
Assert.assertEquals(Sensor.ACTIVE, s.getState()); // no change
ti.tc.rcvMessage = null;
t.requestUpdateFromLayout();
ti.assertSentMessage(":X198F4C4CN0102030405060708;");
Assert.assertEquals(Sensor.ACTIVE, s.getState()); // still no change
ti.sendMessage(":X19544123N0102030405060709;");
Assert.assertEquals(Sensor.INACTIVE, s.getState()); // now it changes
// Actual sequence from sensor table data model
t.setKnownState(Sensor.UNKNOWN);
ti.tc.rcvMessage = null;
t.requestUpdateFromLayout();
ti.assertSentMessage(":X198F4C4CN0102030405060708;");
Assert.assertEquals(Sensor.UNKNOWN, t.getState());
ti.sendMessage(":X19544123N0102030405060709;");
Assert.assertEquals(Sensor.INACTIVE, t.getState());
}
/**
* In this test we simulate the following scenario: A sensor T that is being
* changed locally by JMRI (e.g. due to a panel icon action), which triggers
* a Logix, and in that Logix there is an action that sets a second Sensor
* U. We check that the messages sent to the layout are in the order of
* T:=Active, U:=Active. There was a multiple-year-long regression that
* caused these two events to be sent to the network out of order (U first
* then T).
*/
@Test
public void testListenerOutOfOrder() {
final OlcbSensor u = new OlcbSensor("M", "1.2.3.4.5.6.7.a;1.2.3.4.5.6.7.b", ti.systemConnectionMemo);
final OlcbSensor v = (OlcbSensor)t;
u.finishLoad();
v.setKnownState(Sensor.INACTIVE);
u.setKnownState(Sensor.INACTIVE);
ti.clearSentMessages();
v.addPropertyChangeListener("KnownState", propertyChangeEvent -> {
Assert.assertEquals(Sensor.ACTIVE, t.getKnownState());
u.setKnownState(Sensor.ACTIVE);
});
ThreadingUtil.runOnLayout(() -> v.setKnownState(Sensor.ACTIVE));
Assert.assertEquals(Sensor.ACTIVE, t.getKnownState());
Assert.assertEquals(Sensor.ACTIVE, u.getKnownState());
// Ensures that the last sent message is U==Active. Particularly important that it is NOT
// the message ending with 0708.
ti.assertSentMessage(":X195B4C4CN010203040506070A;");
}
@Test
public void testEventTable() {
EventTable.EventTableEntry[] elist = ti.iface.getEventTable()
.getEventInfo(new EventID("1.2.3.4.5.6.7.8")).getAllEntries();
Assert.assertEquals(1, elist.length);
Assert.assertTrue("Incorrect name: " + elist[0].getDescription(), Pattern.compile("Sensor.*Active").matcher(elist[0].getDescription()).matches());
t.setUserName("MyInput");
elist = ti.iface.getEventTable()
.getEventInfo(new EventID("1.2.3.4.5.6.7.8")).getAllEntries();
Assert.assertEquals(1, elist.length);
Assert.assertEquals("Sensor MyInput Active", elist[0].getDescription());
elist = ti.iface.getEventTable()
.getEventInfo(new EventID("1.2.3.4.5.6.7.9")).getAllEntries();
Assert.assertEquals(1, elist.length);
Assert.assertEquals("Sensor MyInput Inactive", elist[0].getDescription());
}
@Test
public void testSystemSpecificComparisonOfSpecificFormats() {
// test by putting into a tree set, then extracting and checking order
TreeSet<Sensor> set = new TreeSet<>(new NamedBeanComparator<>());
set.add(new OlcbSensor("M", "1.2.3.4.5.6.7.8;1.2.3.4.5.6.7.9", ti.systemConnectionMemo));
set.add(new OlcbSensor("M", "X0501010114FF2000;X0501010114FF2011", ti.systemConnectionMemo));
set.add(new OlcbSensor("M", "X0501010114FF2000;X0501010114FF2001", ti.systemConnectionMemo));
set.add(new OlcbSensor("M", "1.2.3.4.5.6.7.9;1.2.3.4.5.6.7.9", ti.systemConnectionMemo));
Iterator<Sensor> it = set.iterator();
Assert.assertEquals("MS1.2.3.4.5.6.7.8;1.2.3.4.5.6.7.9", it.next().getSystemName());
Assert.assertEquals("MS1.2.3.4.5.6.7.9;1.2.3.4.5.6.7.9", it.next().getSystemName());
Assert.assertEquals("MSX0501010114FF2000;X0501010114FF2001", it.next().getSystemName());
Assert.assertEquals("MSX0501010114FF2000;X0501010114FF2011", it.next().getSystemName());
}
OlcbTestInterface ti;
@BeforeEach
@Override
public void setUp() {
JUnitUtil.setUp();
l = new PropertyChangeListenerScaffold();
// load dummy TrafficController
ti = new OlcbTestInterface(new OlcbTestInterface.CreateConfigurationManager());
ti.waitForStartup();
t = new OlcbSensor("M", "1.2.3.4.5.6.7.8;1.2.3.4.5.6.7.9", ti.systemConnectionMemo);
((OlcbSensor) t).finishLoad();
}
@AfterEach
@Override
public void tearDown() {
t.dispose();
t = null;
l.resetPropertyChanged();
l = null;
ti.dispose();
ti = null;
JUnitUtil.clearShutDownManager(); // put in place because AbstractMRTrafficController implementing subclass was not terminated properly
JUnitUtil.tearDown();
}
}