459 lines
20 KiB
Java
459 lines
20 KiB
Java
package jmri.jmrit.logixng.actions;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
import static org.junit.jupiter.api.Assertions.fail;
|
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import jmri.InstanceManager;
|
|
import jmri.*;
|
|
import jmri.jmrit.logixng.*;
|
|
import jmri.jmrit.logixng.expressions.ExpressionSensor;
|
|
import jmri.jmrit.logixng.expressions.True;
|
|
import jmri.jmrit.logixng.implementation.DefaultConditionalNGScaffold;
|
|
import jmri.script.ScriptEngineSelector;
|
|
import jmri.script.ScriptEngineSelector.Engine;
|
|
import jmri.util.JUnitUtil;
|
|
import jmri.util.JUnitAppender;
|
|
|
|
import org.junit.jupiter.api.AfterEach;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
/**
|
|
* Test ActionSimpleScript
|
|
*
|
|
* @author Daniel Bergqvist 2021
|
|
*/
|
|
public class ActionScriptTest extends AbstractDigitalActionTestBase {
|
|
|
|
private static final String SCRIPT_TEXT = "lights.provideLight(\"IL1\").commandedState = ON";
|
|
private static final String ECMA_SCRIPT = "var DigitalIO = Java.type(\"jmri.DigitalIO\"); lights.provideLight(\"IL1\").setState(DigitalIO.ON);";
|
|
|
|
|
|
private LogixNG logixNG;
|
|
private ConditionalNG conditionalNG;
|
|
private IfThenElse ifThenElse;
|
|
private ActionScript actionScript;
|
|
private Sensor sensor;
|
|
|
|
|
|
@Override
|
|
public ConditionalNG getConditionalNG() {
|
|
return conditionalNG;
|
|
}
|
|
|
|
@Override
|
|
public LogixNG getLogixNG() {
|
|
return logixNG;
|
|
}
|
|
|
|
@Override
|
|
public MaleSocket getConnectableChild() {
|
|
AnalogActionBean childAction = new AnalogActionMemory("IQAA999", null);
|
|
MaleSocket maleSocketChild =
|
|
InstanceManager.getDefault(AnalogActionManager.class).registerAction(childAction);
|
|
return maleSocketChild;
|
|
}
|
|
|
|
@Override
|
|
public String getExpectedPrintedTree() {
|
|
return String.format(
|
|
"Execute script: Single line command. Script lights.provideLight(\"IL1\").commandedState = ON ::: Use default%n");
|
|
}
|
|
|
|
@Override
|
|
public String getExpectedPrintedTreeFromRoot() {
|
|
return String.format(
|
|
"LogixNG: A logixNG%n" +
|
|
" ConditionalNG: A conditionalNG%n" +
|
|
" ! A%n" +
|
|
" If Then Else. Always execute ::: Use default%n" +
|
|
" ? If%n" +
|
|
" Sensor IS1 is Active ::: Use default%n" +
|
|
" ! Then%n" +
|
|
" Execute script: Single line command. Script lights.provideLight(\"IL1\").commandedState = ON ::: Use default%n" +
|
|
" ! Else%n" +
|
|
" Socket not connected%n");
|
|
}
|
|
|
|
@Override
|
|
public NamedBean createNewBean(String systemName) {
|
|
return new ActionScript(systemName, null);
|
|
}
|
|
|
|
@Override
|
|
public boolean addNewSocket() {
|
|
return false;
|
|
}
|
|
|
|
@Test
|
|
public void testCtor() {
|
|
ActionScript action2;
|
|
|
|
action2 = new ActionScript("IQDA321", null);
|
|
action2.setScript(SCRIPT_TEXT);
|
|
assertNotNull( action2, "object exists");
|
|
assertNull( action2.getUserName(), "Username matches");
|
|
assertEquals( "Execute script: Single line command. Script lights.provideLight(\"IL1\").commandedState = ON",
|
|
action2.getLongDescription(), "String matches");
|
|
|
|
action2 = new ActionScript("IQDA321", "My action");
|
|
action2.setScript(SCRIPT_TEXT);
|
|
assertNotNull( action2, "object exists");
|
|
assertEquals( "My action", action2.getUserName(), "Username matches");
|
|
assertEquals( "Execute script: Single line command. Script lights.provideLight(\"IL1\").commandedState = ON",
|
|
action2.getLongDescription(), "String matches");
|
|
|
|
IllegalArgumentException ex = assertThrows( IllegalArgumentException.class,
|
|
() -> {
|
|
ActionScript aScript = new ActionScript("IQA55:12:XY11", null);
|
|
fail("action script created: " + aScript.toString() );
|
|
}, "Illegal system name Expected exception thrown");
|
|
assertNotNull(ex);
|
|
|
|
ex = assertThrows( IllegalArgumentException.class,
|
|
() -> {
|
|
ActionScript aScript = new ActionScript("IQA55:12:XY11", "A name");
|
|
fail("action script created: " + aScript.toString() );
|
|
}, "Illegal system name Expected exception thrown");
|
|
assertNotNull(ex);
|
|
}
|
|
|
|
@Test
|
|
public void testGetChild() {
|
|
// Disable the conditionalNG. This will unregister the listeners
|
|
conditionalNG.setEnabled(false);
|
|
|
|
// Test without script
|
|
actionScript.setScript(null);
|
|
assertEquals( 0, actionScript.getChildCount(), "getChildCount() returns 0");
|
|
|
|
UnsupportedOperationException ex = assertThrows( UnsupportedOperationException.class,
|
|
() -> actionScript.getChild(0),
|
|
"Exception is thrown");
|
|
assertEquals( "Not supported.", ex.getMessage(), "Error message is correct");
|
|
|
|
// Test with script
|
|
actionScript.setScript(SCRIPT_TEXT);
|
|
assertEquals( 0, actionScript.getChildCount(), "getChildCount() returns 0");
|
|
}
|
|
|
|
@Test
|
|
public void testDescription() {
|
|
assertEquals("Script", actionScript.getShortDescription());
|
|
assertEquals("Execute script: Single line command. Script lights.provideLight(\"IL1\").commandedState = ON",
|
|
actionScript.getLongDescription());
|
|
}
|
|
|
|
@Test
|
|
public void testAction_SingleJythonCommand() throws JmriException {
|
|
// Test action
|
|
Light light = InstanceManager.getDefault(LightManager.class).provide("IL1");
|
|
light.setCommandedState(Light.OFF);
|
|
|
|
// The action is not yet executed so the light should be off
|
|
assertTrue( light.getState() == Light.OFF, "light is off");
|
|
// Enable the conditionalNG and all its children.
|
|
conditionalNG.setEnabled(true);
|
|
// Set the sensor to execute the conditionalNG
|
|
sensor.setState(Sensor.ACTIVE);
|
|
// The action should now be executed so the light should be on
|
|
assertTrue( light.getState() == Light.ON, "light is on");
|
|
|
|
|
|
// Test action when triggered because the script is listening on the sensor IS2
|
|
Sensor sensor2 = InstanceManager.getDefault(SensorManager.class).provide("IS2");
|
|
sensor2.setCommandedState(Sensor.INACTIVE);
|
|
light.setCommandedState(Light.OFF);
|
|
|
|
logixNG.unregisterListeners();
|
|
|
|
// Disconnect the expressionSensor and replace it with a True expression
|
|
// since we always want the result "true" for this test.
|
|
ifThenElse.getChild(0).disconnect();
|
|
True expressionTrue = new True("IQDE322", null);
|
|
MaleSocket maleSocketTrue =
|
|
InstanceManager.getDefault(DigitalExpressionManager.class).registerExpression(expressionTrue);
|
|
ifThenElse.getChild(0).connect(maleSocketTrue);
|
|
|
|
// actionScript.setScript(_scriptText);
|
|
|
|
// The action is not yet executed so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Activate the sensor. This should not execute the conditional.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// The conditionalNG is not yet enabled so it shouldn't be executed.
|
|
// So the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Inactivate the sensor. This should not execute the conditional.
|
|
sensor2.setCommandedState(Sensor.INACTIVE);
|
|
// The action is not yet executed so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Enable the conditionalNG and all its children.
|
|
conditionalNG.setEnabled(true);
|
|
// Activate the sensor. This should execute the conditional.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// The action should now be executed so the atomic boolean should be true
|
|
assertEquals( Light.ON, light.getState(), "light is on");
|
|
|
|
// Unregister listeners
|
|
actionScript.unregisterListeners();
|
|
light.setState(Light.OFF);
|
|
// Turn the light off.
|
|
light.setCommandedState(Light.OFF);
|
|
// Activate the sensor. This not should execute the conditional since listerners are not registered.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// Listerners are not registered so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
|
|
// Test execute() without script. This shouldn't do anything but we
|
|
// do it for coverage.
|
|
actionScript.setScript("");
|
|
actionScript.execute();
|
|
}
|
|
|
|
@Test
|
|
@SuppressWarnings("null") // engine false positive, should be fixed in JUnit6
|
|
public void testAction_SingleEcmaCommand() throws JmriException {
|
|
actionScript.getScriptEngineSelector().setSelectedEngine(ScriptEngineSelector.ECMA_SCRIPT);
|
|
|
|
// Java 17 doesn't have ECMA_SCRIPT
|
|
JUnitAppender.suppressWarnMessage("Cannot select engine for the language ECMAScript");
|
|
Engine engine = actionScript.getScriptEngineSelector().getSelectedEngine();
|
|
assumeTrue( engine != null, "Engine not null");
|
|
assertNotNull(engine);
|
|
assumeTrue(engine.getLanguageName().equals(ScriptEngineSelector.ECMA_SCRIPT),
|
|
() -> "Engine Language Name was \"" + engine.getLanguageName() + "\" not \"" + ScriptEngineSelector.ECMA_SCRIPT+"\"");
|
|
|
|
actionScript.setScript(ECMA_SCRIPT);
|
|
|
|
// Test action
|
|
Light light = InstanceManager.getDefault(LightManager.class).provide("IL1");
|
|
light.setCommandedState(Light.OFF);
|
|
|
|
// The action is not yet executed so the light should be off
|
|
assertTrue( light.getState() == Light.OFF, "light is off");
|
|
// Enable the conditionalNG and all its children.
|
|
conditionalNG.setEnabled(true);
|
|
// Set the sensor to execute the conditionalNG
|
|
sensor.setState(Sensor.ACTIVE);
|
|
// The action should now be executed so the light should be on
|
|
assertTrue( light.getState() == Light.ON, "light is on");
|
|
|
|
|
|
// Test action when triggered because the script is listening on the sensor IS2
|
|
Sensor sensor2 = InstanceManager.getDefault(SensorManager.class).provide("IS2");
|
|
sensor2.setCommandedState(Sensor.INACTIVE);
|
|
light.setCommandedState(Light.OFF);
|
|
|
|
logixNG.unregisterListeners();
|
|
|
|
// Disconnect the expressionSensor and replace it with a True expression
|
|
// since we always want the result "true" for this test.
|
|
ifThenElse.getChild(0).disconnect();
|
|
True expressionTrue = new True("IQDE322", null);
|
|
MaleSocket maleSocketTrue =
|
|
InstanceManager.getDefault(DigitalExpressionManager.class).registerExpression(expressionTrue);
|
|
ifThenElse.getChild(0).connect(maleSocketTrue);
|
|
|
|
// actionScript.setScript(_scriptText);
|
|
|
|
// The action is not yet executed so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Activate the sensor. This should not execute the conditional.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// The conditionalNG is not yet enabled so it shouldn't be executed.
|
|
// So the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Inactivate the sensor. This should not execute the conditional.
|
|
sensor2.setCommandedState(Sensor.INACTIVE);
|
|
// The action is not yet executed so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Enable the conditionalNG and all its children.
|
|
conditionalNG.setEnabled(true);
|
|
// Activate the sensor. This should execute the conditional.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// The action should now be executed so the atomic boolean should be true
|
|
assertEquals( Light.ON, light.getState(), "light is on");
|
|
|
|
// Unregister listeners
|
|
actionScript.unregisterListeners();
|
|
light.setState(Light.OFF);
|
|
// Turn the light off.
|
|
light.setCommandedState(Light.OFF);
|
|
// Activate the sensor. This not should execute the conditional since listerners are not registered.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// Listerners are not registered so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
|
|
// Test execute() without script. This shouldn't do anything but we
|
|
// do it for coverage.
|
|
actionScript.setScript("");
|
|
actionScript.execute();
|
|
}
|
|
|
|
@Test
|
|
public void testAction_RunScript() throws JmriException {
|
|
|
|
actionScript.setOperationType(ActionScript.OperationType.RunScript);
|
|
actionScript.setScript("java/test/jmri/jmrit/logixng/actions/ActionScriptTest.py");
|
|
|
|
// Test action
|
|
Light light = InstanceManager.getDefault(LightManager.class).provide("IL9");
|
|
light.setCommandedState(Light.OFF);
|
|
|
|
// The action is not yet executed so the light should be off
|
|
assertTrue( light.getState() == Light.OFF, "light is off");
|
|
// Enable the conditionalNG and all its children.
|
|
conditionalNG.setEnabled(true);
|
|
// Set the sensor to execute the conditionalNG
|
|
sensor.setState(Sensor.ACTIVE);
|
|
// The action should now be executed so the light should be on
|
|
assertTrue( light.getState() == Light.ON, "light is on");
|
|
|
|
|
|
// Test action when triggered because the script is listening on the sensor IS2
|
|
Sensor sensor2 = InstanceManager.getDefault(SensorManager.class).provide("IS2");
|
|
sensor2.setCommandedState(Sensor.INACTIVE);
|
|
light.setCommandedState(Light.OFF);
|
|
|
|
logixNG.unregisterListeners();
|
|
|
|
// Disconnect the expressionSensor and replace it with a True expression
|
|
// since we always want the result "true" for this test.
|
|
ifThenElse.getChild(0).disconnect();
|
|
True expressionTrue = new True("IQDE322", null);
|
|
MaleSocket maleSocketTrue =
|
|
InstanceManager.getDefault(DigitalExpressionManager.class).registerExpression(expressionTrue);
|
|
ifThenElse.getChild(0).connect(maleSocketTrue);
|
|
|
|
// The action is not yet executed so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Activate the sensor. This should not execute the conditional.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// The conditionalNG is not yet enabled so it shouldn't be executed.
|
|
// So the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Inactivate the sensor. This should not execute the conditional.
|
|
sensor2.setCommandedState(Sensor.INACTIVE);
|
|
// The action is not yet executed so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
// Enable the conditionalNG and all its children.
|
|
conditionalNG.setEnabled(true);
|
|
// Activate the sensor. This should execute the conditional.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// The action should now be executed so the atomic boolean should be true
|
|
assertEquals( Light.ON, light.getState(), "light is on");
|
|
|
|
// Unregister listeners
|
|
actionScript.unregisterListeners();
|
|
light.setState(Light.OFF);
|
|
// Turn the light off.
|
|
light.setCommandedState(Light.OFF);
|
|
// Activate the sensor. This not should execute the conditional since listerners are not registered.
|
|
sensor2.setCommandedState(Sensor.ACTIVE);
|
|
// Listerners are not registered so the atomic boolean should be false
|
|
assertEquals( Light.OFF, light.getState(), "light is off");
|
|
|
|
// Test execute() without script. This shouldn't do anything but we
|
|
// do it for coverage.
|
|
actionScript.setScript("");
|
|
actionScript.execute();
|
|
JUnitAppender.assertWarnMessage("cannot execute script \"\"");
|
|
}
|
|
|
|
@Test
|
|
public void testAction_GetAndSetLocalVariables() throws JmriException {
|
|
|
|
((MaleSocket)ifThenElse.getParent()).addLocalVariable("in", SymbolTable.InitialValueType.Integer, "10");
|
|
var globalVariable = InstanceManager.getDefault(GlobalVariableManager.class).createGlobalVariable("out");
|
|
|
|
actionScript.setScript("symbolTable.setValue(\"out\",symbolTable.getValue(\"in\")*15)");
|
|
|
|
// Enable the conditionalNG and all its children.
|
|
conditionalNG.setEnabled(true);
|
|
// Set the sensor to execute the conditionalNG
|
|
sensor.setState(Sensor.ACTIVE);
|
|
|
|
// The action should now be executed so the global variable should have the correct value
|
|
assertEquals( 150,
|
|
((java.math.BigInteger)globalVariable.getValue()).longValue(),
|
|
"global variable has the correct value");
|
|
}
|
|
|
|
@Test
|
|
public void testSetScript() {
|
|
// Disable the conditionalNG. This will unregister the listeners
|
|
conditionalNG.setEnabled(false);
|
|
|
|
// Test setScript() when listeners are registered
|
|
actionScript.setScript(SCRIPT_TEXT);
|
|
assertNotNull( actionScript.getScript(), "Script is not null");
|
|
|
|
// Test bad script
|
|
actionScript.setScript("This is a bad script");
|
|
assertEquals("This is a bad script", actionScript.getScript());
|
|
}
|
|
|
|
@BeforeEach
|
|
public void setUp() throws SocketAlreadyConnectedException {
|
|
JUnitUtil.setUp();
|
|
JUnitUtil.resetInstanceManager();
|
|
JUnitUtil.resetProfileManager();
|
|
JUnitUtil.initConfigureManager();
|
|
JUnitUtil.initInternalSensorManager();
|
|
JUnitUtil.initInternalLightManager();
|
|
JUnitUtil.initLogixNGManager();
|
|
|
|
_category = LogixNG_Category.ITEM;
|
|
_isExternal = true;
|
|
|
|
logixNG = InstanceManager.getDefault(LogixNG_Manager.class).createLogixNG("A logixNG");
|
|
conditionalNG = new DefaultConditionalNGScaffold("IQC1", "A conditionalNG"); // NOI18N;
|
|
InstanceManager.getDefault(ConditionalNG_Manager.class).register(conditionalNG);
|
|
logixNG.addConditionalNG(conditionalNG);
|
|
conditionalNG.setRunDelayed(false);
|
|
conditionalNG.setEnabled(true);
|
|
|
|
ifThenElse = new IfThenElse("IQDA321", null);
|
|
ifThenElse.setExecuteType(IfThenElse.ExecuteType.AlwaysExecute);
|
|
MaleSocket maleSocket =
|
|
InstanceManager.getDefault(DigitalActionManager.class).registerAction(ifThenElse);
|
|
conditionalNG.getChild(0).connect(maleSocket);
|
|
|
|
sensor = InstanceManager.getDefault(SensorManager.class).provide("IS1");
|
|
|
|
ExpressionSensor expressionSensor = new ExpressionSensor("IQDE321", null);
|
|
expressionSensor.getSelectNamedBean().setNamedBean(sensor);
|
|
MaleSocket maleSocket2 =
|
|
InstanceManager.getDefault(DigitalExpressionManager.class).registerExpression(expressionSensor);
|
|
ifThenElse.getChild(0).connect(maleSocket2);
|
|
|
|
actionScript = new ActionScript(InstanceManager.getDefault(DigitalActionManager.class).getAutoSystemName(), null);
|
|
actionScript.setScript(SCRIPT_TEXT);
|
|
MaleSocket socketActionSimpleScript = InstanceManager.getDefault(DigitalActionManager.class).registerAction(actionScript);
|
|
ifThenElse.getChild(1).connect(socketActionSimpleScript);
|
|
|
|
_base = actionScript;
|
|
_baseMaleSocket = socketActionSimpleScript;
|
|
|
|
assertTrue( logixNG.setParentForAllChildren(new ArrayList<>()));
|
|
logixNG.activate();
|
|
logixNG.setEnabled(true);
|
|
}
|
|
|
|
@AfterEach
|
|
public void tearDown() {
|
|
jmri.jmrit.logixng.util.LogixNG_Thread.stopAllLogixNGThreads();
|
|
JUnitUtil.deregisterBlockManagerShutdownTask();
|
|
JUnitUtil.tearDown();
|
|
}
|
|
|
|
}
|