package jmri.util; import static org.junit.jupiter.api.Assertions.fail; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.awt.*; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.List; import java.util.concurrent.Callable; import java.util.function.Supplier; import javax.annotation.CheckForNull; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.swing.AbstractButton; import jmri.*; import jmri.implementation.JmriConfigurationManager; import jmri.jmrit.blockboss.BlockBossLogicProvider; import jmri.jmrit.display.EditorFrameOperator; import jmri.jmrit.display.EditorManager; import jmri.jmrit.display.layoutEditor.LayoutBlockManager; import jmri.jmrit.logix.OBlockManager; import jmri.jmrit.logix.WarrantManager; import jmri.jmrit.logixng.*; import jmri.jmrit.logixng.implementation.DefaultLogixNGManager; import jmri.jmrit.logixng.implementation.DefaultConditionalNGManager; import jmri.jmrit.logixng.implementation.DefaultAnalogActionManager; import jmri.jmrit.logixng.implementation.DefaultAnalogExpressionManager; import jmri.jmrit.logixng.implementation.DefaultDigitalActionManager; import jmri.jmrit.logixng.implementation.DefaultDigitalBooleanActionManager; import jmri.jmrit.logixng.implementation.DefaultDigitalExpressionManager; import jmri.jmrit.logixng.implementation.DefaultStringActionManager; import jmri.jmrit.logixng.implementation.DefaultStringExpressionManager; import jmri.jmrit.roster.RosterConfigManager; import jmri.jmrix.ConnectionConfigManager; import jmri.jmrix.debugthrottle.DebugThrottleManager; import jmri.jmrix.internal.*; import jmri.managers.*; import jmri.profile.*; import jmri.progdebugger.DebugProgrammerManager; import jmri.util.gui.GuiLafPreferencesManager; import jmri.util.managers.*; import jmri.util.prefs.*; import jmri.util.zeroconf.MockZeroConfServiceManager; import jmri.util.zeroconf.ZeroConfServiceManager; import org.slf4j.event.Level; import org.junit.jupiter.api.Assertions; import org.netbeans.jemmy.*; import org.netbeans.jemmy.operators.*; /** * Common utility methods for working with JUnit. *
* To release the current thread and allow other listeners to execute: Note that this is not appropriate for Swing objects; you need
* to use Jemmy for that.
*
* JUnitUtil.waitFor(int time);
*
* If you're using the InstanceManager, setUp() implementation should start * with: *
* JUnitUtil.setUp();
* JUnitUtil.initInternalTurnoutManager();
* JUnitUtil.initInternalLightManager();
* JUnitUtil.initInternalSensorManager();
* JUnitUtil.initDebugThrottleManager();
*
* * Your tearDown() should end with: *
* JUnitUtil.tearDown();
*
* * Note that memory managers and some others are completely internal, and will * be reset when you reset the instance manager. *
* Messages originating in this class are printed via System.err instead of * being logged because this class doesn't assume logging has been initialized. * * @author Bob Jacobsen Copyright 2009, 2015, 2026 * @since 2.5.3 */ public class JUnitUtil { /** * Standard time (in mSec) to wait when releasing * a thread during a test. *
* The method releaseThread() is removed but this constant is still used * by some tests when calling waitFor(int time). *
* Public in case modification is needed from a test or script. */ public static final int WAITFOR_DEFAULT_DELAY = 50; /** * Default standard time step (in mSec) when looping in a waitFor operation. */ protected static final int DEFAULT_WAITFOR_DELAY_STEP = 5; /** * Standard time step (in mSec) when looping in a waitFor operation. *
* Public in case modification is needed from a test or script. * This value is always reset to {@value #DEFAULT_WAITFOR_DELAY_STEP} * during setUp(). */ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "MS_CANNOT_BE_FINAL", justification = "value reset during setUp() ") public static int WAITFOR_DELAY_STEP = DEFAULT_WAITFOR_DELAY_STEP; /** * Default maximum time to wait before failing a waitFor operation. *
* The default value is really long, but that only matters when the test * is failing anyway, and some of the LayoutEditor/SignalMastLogic tests * are slow. But too long will cause CI jobs to time out before this logs * the error.... */ protected static final int DEFAULT_WAITFOR_MAX_DELAY = 10000; /** * Maximum time to wait before failing a waitFor operation. *
* Public in case modification is needed from a test or script. * This value is always reset to {@value #DEFAULT_WAITFOR_MAX_DELAY} during setUp(). */ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "MS_CANNOT_BE_FINAL", justification = "value reset during setUp() ") public static int WAITFOR_MAX_DELAY = DEFAULT_WAITFOR_MAX_DELAY; /** * When true, prints each setUp method to help identify which tests include a failure. * When checkSetUpTearDownSequence is also true, this also prints on execution of tearDown. *
* Set from the jmri.util.JUnitUtil.printSetUpTearDownNames environment variable. */ static boolean printSetUpTearDownNames = Boolean.getBoolean("jmri.util.JUnitUtil.printSetUpTearDownNames"); // false unless set true /** * When true, checks that calls to setUp and tearDown properly alterante, printing an * error message with context information on System.err if inconsistent calls are observed. *
* Set from the jmri.util.JUnitUtil.checkSetUpTearDownSequence environment variable. */ static boolean checkSetUpTearDownSequence = Boolean.getBoolean("jmri.util.JUnitUtil.checkSetUpTearDownSequence"); // false unless set true /** * Adds extensive error information to the output of checkSetUpTearDownSequence. * Note: The context checking and storage required for this takes a lot of time. *
* Set from the jmri.util.JUnitUtil.checkSequenceDumpsStack environment variable. */ static boolean checkSequenceDumpsStack = Boolean.getBoolean("jmri.util.JUnitUtil.checkSequenceDumpsStack"); // false unless set true /** * If true, will cause the checkSetUpTearDownSequence check to * fail the test in addition to logging. *
* Set from the jmri.util.JUnitUtil.checkSequenceFailsTest environment variable. */ static boolean checkSequenceFailsTest = Boolean.getBoolean("jmri.util.JUnitUtil.checkSequenceFailsTest"); // false unless set true /** * Announce any threads left behind after a test calls {@link #tearDown} *
* Set from the jmri.util.JUnitUtil.checkRemnantThreads environment variable. */ // static boolean checkRemnantThreads = true; static boolean checkRemnantThreads = Boolean.getBoolean("jmri.util.JUnitUtil.checkRemnantThreads"); // false unless set true /** * Fail Test if any threads left behind after a test calls {@link #tearDown} *
* Set from the jmri.util.JUnitUtil.failRemnantThreads environment variable. */ // static boolean failRemnantThreads = true; static boolean failRemnantThreads = Boolean.getBoolean("jmri.util.JUnitUtil.failRemnantThreads"); // false unless set true /** * Kill any threads left behind after a test calls {@link #tearDown} *
* Set from the jmri.util.JUnitUtil.killRemnantThreads environment variable */ static boolean killRemnantThreads = Boolean.getBoolean("jmri.util.JUnitUtil.killRemnantThreads"); // false unless set true /** * Check for tests that take an excessive time *
* Set from the jmri.util.JUnitUtil.checkTestDuration environment variable.
*/
static boolean checkTestDuration = Boolean.getBoolean("jmri.util.JUnitUtil.checkTestDuration"); // false unless set true
static long checkTestDurationMax = Long.getLong("jmri.util.JUnitUtil.checkTestDurationMax", 5000); // milliseconds
static long checkTestDurationStartTime = 0; // working value
private static boolean didSetUp = false; // If true, last saw setUp, waiting tearDown normally
private static boolean didTearDown = true; // If true, last saw tearDown, waiting setUp normally
private static String lastSetUpClassName = "
* One or the other of {@link #setUp()} or {@link #setUpLoggingAndCommonProperties()} must
* be present in the {@code @BeforeEach} routine.
*
*/
public static void setUpLoggingAndCommonProperties() {
if (!isLoggingInitialized) {
// init logging if needed
isLoggingInitialized = true;
String filename = System.getProperty("jmri.log4jconfigfilename", "tests_lcf.xml");
TestingLoggerConfiguration.initLogging(filename);
}
// need to do this each time
try {
JUnitAppender.startLogging();
// reset warn _only_ once logic to make tests repeatable
JUnitLoggingUtil.restartWarnOnce();
// ensure logging of deprecated method calls;
// individual tests can turn off as needed
JUnitLoggingUtil.setDeprecatedLogging(true);
} catch (Throwable e) {
System.err.println("Could not start JUnitAppender, but test continues:\n" + e);
}
// clear the backlog and reset the UnexpectedMessageFlags so that
// errors from a previous test do not interfere with the current test.
JUnitAppender.clearBacklog();
JUnitAppender.resetUnexpectedMessageFlags(Level.INFO);
// do not set the UncaughtExceptionHandler while unit testing
// individual tests can explicitly set it after calling this method
Thread.setDefaultUncaughtExceptionHandler(null);
// make sure the jmri.prefsdir property match the property passed
// to the tests.
if (initPrefsDir == null) {
initPrefsDir = System.getProperty("jmri.prefsdir", "./temp");
}
System.setProperty("jmri.prefsdir", initPrefsDir);
// silence the Jemmy GUI unit testing framework
JUnitUtil.silenceGUITestOutput();
// ideally this would be resetWindows(false, true) to force an error if an earlier
// test left a window open, but different platforms seem to have just
// enough differences that this is, for now, turned off
resetWindows(false, false, "before");
// Log and/or check the use of setUp and tearDown
if (checkSetUpTearDownSequence || printSetUpTearDownNames) {
lastSetUpClassName = getTestClassName();
if (printSetUpTearDownNames) System.err.println(">> Starting test in "+lastSetUpClassName);
if ( checkSetUpTearDownSequence) {
if (checkSequenceDumpsStack) lastSetUpThreadName = Thread.currentThread().getName();
if (didSetUp || ! didTearDown) {
System.err.println(" "+getTestClassName()+".setUp on thread "+lastSetUpThreadName+" unexpectedly found setUp="+didSetUp+" tearDown="+didTearDown+"; last setUp was in "+lastSetUpClassName+" thread "+lastSetUpThreadName);
if (checkSequenceDumpsStack) {
System.err.println("---- This stack ------");
Thread.dumpStack();
System.err.println("---- Last setUp stack ------");
for (StackTraceElement e : lastSetUpStackTrace) System.err.println(" at " + e);
System.err.println("---- Last tearDown stack ------");
for (StackTraceElement e : lastTearDownStackTrace) System.err.println(" at " + e);
System.err.println("----------------------");
}
if (checkSequenceFailsTest) {
Assertions.fail("setUp and tearDown did not match");
}
}
didTearDown = false;
didSetUp = true;
if (checkSequenceDumpsStack) lastSetUpStackTrace = Thread.currentThread().getStackTrace();
}
}
// checking time?
if (checkTestDuration) {
checkTestDurationStartTime = System.currentTimeMillis();
}
}
/**
* JMRI standard setUp for tests.
* This should be the first line in the {@code @BeforeEach}
* annotated method if the tests do not mock the InstanceManager.
*
* One or the other of {@link #setUp()} or {@link #setUpLoggingAndCommonProperties()} must
* be present in the {@code @BeforeEach} routine.
*
* Calls {@link #setUpLoggingAndCommonProperties()}, {@link #resetInstanceManager()}
* and sets the jmri.configurexml.ShutdownPreferences setEnableStoreCheck to false.
*/
public static void setUp() {
WAITFOR_DELAY_STEP = DEFAULT_WAITFOR_DELAY_STEP;
WAITFOR_MAX_DELAY = DEFAULT_WAITFOR_MAX_DELAY;
// all the setup for a MockInstanceManager applies
setUpLoggingAndCommonProperties();
// Do a minimal amount of de-novo setup
resetInstanceManager();
InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).setEnableStoreCheck(false);
}
/**
* Silently remove any AbstractTurnout threads that are still running.
* A bit expensive, so only used when needed.
*/
public static void clearTurnoutThreads(){
removeMatchingThreads("setCommandedStateAtInterval"); // must stay consistent with AbstractTurnout
}
/**
* Silently remove any DefaultRoute threads that are still running.
* A bit expensive, so only used when needed.
*/
public static void clearRouteThreads(){
removeMatchingThreads("setRoute"); // must stay consistent with DefaultRoute
}
/**
* Silently remove any blockboss/Simple Signal Logic threads that are still running.
* A bit expensive, so only used when needed.
*/
public static void clearBlockBossLogicThreads(){
removeMatchingThreads("BlockBossLogic");
}
/**
* Utility to remove any threads with a matching name
* @param nameContains The thread name to search
*/
public static void removeMatchingThreads(String nameContains) {
ThreadGroup main = Thread.currentThread().getThreadGroup();
while (main.getParent() != null ) {main = main.getParent(); }
Thread[] list = new Thread[main.activeCount()+2]; // space on end
int max = main.enumerate(list);
for (int i = 0; i
* To be used in tests, will do an assert if the total delay is longer than
* WAITFOR_MAX_DELAY
*
* Typical use:
*
* To be used in tests, will fail test if the total delay is longer than
* WAITFOR_MAX_DELAY.
*
* The messageSupplier is not evaluated unless there is a test failure so
* can include expensive method calls and string joins without penalty.
*
* Typical use:
*
* To be used in assumptions, will return false if the total delay is longer
* than WAITFOR_MAX_DELAY
*
* Typical use:
*
* It's better to wait for a condition, but if you can't find a condition,
* this will have to do.
*
*
* @param msec Delay in milliseconds
*/
public static void waitFor(int msec) {
if (javax.swing.SwingUtilities.isEventDispatchThread()) {
log.error("Cannot use waitFor on Swing thread", new Exception());
return;
}
int delay = 0;
try {
while (delay < msec) {
int priority = Thread.currentThread().getPriority();
try {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
Thread.sleep(WAITFOR_DELAY_STEP);
delay += WAITFOR_DELAY_STEP;
} catch (InterruptedException e) {
return;
} finally {
Thread.currentThread().setPriority(priority);
}
}
} catch (Exception ex) {
log.error("Exception in waitFor condition.", ex);
}
}
/**
* Wait for a specific condition to be true, without having to wait longer
*
* To be used in tests, will do an assert if the total delay is longer than
* 1 second
*
* Typical use:
*
* To be used in assumptions, will return false if the total delay is longer
* than 1000 milliseconds.
*
* Typical use:
*
* You can't assume that all the consequences of that setting will have
* propagated through when this returns; those might take a long time. But
* the set operation itself will be complete.
*
* @param bean the bean
* @param state the desired state
*/
public static void setBeanState(NamedBean bean, int state) {
try {
javax.swing.SwingUtilities.invokeAndWait(
() -> {
try {
bean.setState(state);
} catch (JmriException e) {
log.error("Threw exception while setting state: ", e);
}
}
);
} catch (InterruptedException e) {
log.warn("Interrupted while setting state: ", e);
} catch (InvocationTargetException e) {
log.warn("Failed during invocation while setting state: ", e);
}
}
/**
* Set a NamedBean (Turnout, Sensor, SignalHead, ...) to a specific value in
* a thread-safe way, including waiting for the state to appear.
*
* You can't assume that all the consequences of that setting will have
* propagated through when this returns; those might take a long time. But
* the set operation itself will be complete.
*
* @param bean the bean
* @param state the desired state
*/
public static void setBeanStateAndWait(NamedBean bean, int state) {
setBeanState(bean, state);
JUnitUtil.waitFor(() -> {
return state == bean.getState();
}, "setAndWait " + bean.getSystemName() + ": " + state);
}
/**
* Reset the Instance Manager.
* Clears all instances from the static InstanceManager.
*
* Ensures the auto-default UserPreferencesManager is not created
* by installing a test one.
*
* Sets the jmri.configurexml.ShutdownPreferences setEnableStoreCheck to false.
*/
public static void resetInstanceManager() {
// clear all instances from the static InstanceManager
InstanceManager.getDefault().clearAll();
// ensure the auto-default UserPreferencesManager is not created by installing a test one
InstanceManager.setDefault(UserPreferencesManager.class, new TestUserPreferencesManager());
InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).setEnableStoreCheck(false);
}
public static void resetTurnoutOperationManager() {
InstanceManager.reset(TurnoutOperationManager.class);
InstanceManager.getDefault(TurnoutOperationManager.class); // force creation
}
public static void initConfigureManager() {
InstanceManager.setDefault(ConfigureManager.class, new JmriConfigurationManager());
}
public static void initDefaultUserMessagePreferences() {
// remove the existing user preferences manager (if present)
InstanceManager.reset(UserPreferencesManager.class);
// create a test user preferences manager
InstanceManager.setDefault(UserPreferencesManager.class, new TestUserPreferencesManager());
}
public static void initInternalTurnoutManager() {
// now done automatically by InstanceManager's autoinit
InstanceManager.turnoutManagerInstance();
}
public static void initInternalLightManager() {
// now done automatically by InstanceManager's autoinit
InstanceManager.lightManagerInstance();
}
public static void initInternalSensorManager() {
// now done automatically by InstanceManager's autoinit
InstanceManager.sensorManagerInstance();
InternalSensorManager.setDefaultStateForNewSensors(jmri.Sensor.UNKNOWN);
}
public static void initRouteManager() {
// routes provide sensors, so ensure the sensor manager is initialized
// routes need turnouts, so ensure the turnout manager is initialized
JUnitUtil.initInternalSensorManager();
JUnitUtil.initInternalTurnoutManager();
// now done automatically by InstanceManager's autoinit
InstanceManager.getDefault(RouteManager.class);
}
public static void initMemoryManager() {
MemoryManager m = new DefaultMemoryManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m, jmri.Manager.MEMORIES);
}
}
public static void initReporterManager() {
ReporterManager m = new InternalReporterManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m, jmri.Manager.REPORTERS);
}
}
public static void initOBlockManager() {
OBlockManager b = new OBlockManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(b, jmri.Manager.OBLOCKS);
}
}
public static void deregisterBlockManagerShutdownTask() {
if (! InstanceManager.isInitialized(ShutDownManager.class)) return;
if (! InstanceManager.isInitialized(BlockManager.class)) return;
InstanceManager
.getDefault(ShutDownManager.class)
.deregister(InstanceManager.getDefault(BlockManager.class).shutDownTask);
}
public static void initWarrantManager() {
WarrantManager w = new WarrantManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(w, jmri.Manager.WARRANTS);
}
}
public static void initSignalMastLogicManager() {
SignalMastLogicManager w = new DefaultSignalMastLogicManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(w, jmri.Manager.SIGNALMASTLOGICS);
}
}
public static void initLayoutBlockManager() {
LayoutBlockManager w = new LayoutBlockManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(w, jmri.Manager.LAYOUTBLOCKS);
}
}
public static void initSectionManager() {
jmri.SectionManager w = new jmri.managers.DefaultSectionManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(w, jmri.Manager.SECTIONS);
}
}
public static void initInternalSignalHeadManager() {
SignalHeadManager m = new AbstractSignalHeadManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
InstanceManager.setDefault(SignalHeadManager.class, m);
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m, jmri.Manager.SIGNALHEADS);
}
}
public static void initDefaultSignalMastManager() {
InstanceManager.setDefault(SignalMastManager.class, new DefaultSignalMastManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));
}
public static void initDebugCommandStation() {
jmri.CommandStation cs = new jmri.CommandStation() {
@Override
public boolean sendPacket(@Nonnull byte[] packet, int repeats) {
return true;
}
@Override
public String getUserName() {
return "testCS";
}
@Override
public String getSystemPrefix() {
return "I";
}
};
InstanceManager.store(cs, jmri.CommandStation.class);
}
/**
* Creates a new DebugThrottleManager using the default
* InternalSystemConnectionMemo.
* Stores to both InstanceManager and System Connection.
*/
public static void initDebugThrottleManager() {
var memo = InstanceManager.getDefault(InternalSystemConnectionMemo.class);
initDebugThrottleManager(memo);
}
/**
* Creates a new DebugThrottleManager using the supplied SystemConnectionMemo.
* Stores to both InstanceManager and System Connection.
* @param memo the system connection to use.
*/
public static void initDebugThrottleManager(@Nonnull jmri.jmrix.DefaultSystemConnectionMemo memo) {
ThrottleManager m = new DebugThrottleManager(memo);
memo.store(m, ThrottleManager.class);
InstanceManager.store(m, ThrottleManager.class);
}
public static void initDebugProgrammerManager() {
DebugProgrammerManager m = new DebugProgrammerManager();
InstanceManager.store(m, AddressedProgrammerManager.class);
InstanceManager.store(m, GlobalProgrammerManager.class);
}
public static void initDebugPowerManager() {
InstanceManager.setDefault(PowerManager.class, new PowerManagerScaffold());
}
/**
* Initialize an {@link IdTagManager} that does not use persistent storage.
* If needing an IdTagManager that does use persistent storage use
* {@code InstanceManager.setDefault(IdTagManager.class, new DefaultIdTagManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));}
* to initialize an IdTagManager in the {@code @Before} annotated method of
* the test class or allow the {@link DefaultIdTagManager} to be
* automatically initialized when needed.
*/
public static void initIdTagManager() {
InstanceManager.reset(IdTagManager.class);
InstanceManager.setDefault(IdTagManager.class,
new DefaultIdTagManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)) {
@Override
public void writeIdTagDetails() {
// do not actually write tags
this.dirty = false;
}
@Override
public void readIdTagDetails() {
// do not actually read tags
this.dirty = false;
}
@Override
protected void initShutdownTask(){
//don't even register the shutdownTask
}
});
}
public static void initRailComManager() {
InstanceManager.reset(jmri.RailComManager.class);
InstanceManager.store(new DefaultRailComManager(), jmri.RailComManager.class);
}
public static void initLogixManager() {
LogixManager m = new DefaultLogixManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m, jmri.Manager.LOGIXS);
}
}
public static void initConditionalManager() {
ConditionalManager m = new DefaultConditionalManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m, jmri.Manager.CONDITIONALS);
}
}
public static void initInternalTurnoutManagerThrowException() {
InstanceManager.setDefault(TurnoutManager.class, new TurnoutManagerThrowExceptionScaffold());
}
public static void initLogixNGManager() {
initLogixNGManager(true);
}
public static void initLogixNGManager(boolean activate) {
LogixNG_Manager m1 = new DefaultLogixNGManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m1, jmri.Manager.LOGIXNGS);
}
InstanceManager.setDefault(LogixNG_Manager.class, m1);
ConditionalNG_Manager m2 = new DefaultConditionalNGManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m2, jmri.Manager.LOGIXNG_CONDITIONALNGS);
}
InstanceManager.setDefault(ConditionalNG_Manager.class, m2);
AnalogActionManager m3 = new DefaultAnalogActionManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m3, jmri.Manager.LOGIXNG_ANALOG_ACTIONS);
}
InstanceManager.setDefault(AnalogActionManager.class, m3);
AnalogExpressionManager m4 = new DefaultAnalogExpressionManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m4, jmri.Manager.LOGIXNG_ANALOG_EXPRESSIONS);
}
InstanceManager.setDefault(AnalogExpressionManager.class, m4);
DigitalActionManager m5 = new DefaultDigitalActionManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m5, jmri.Manager.LOGIXNG_DIGITAL_ACTIONS);
}
InstanceManager.setDefault(DigitalActionManager.class, m5);
DigitalBooleanActionManager m6 = new DefaultDigitalBooleanActionManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m6, jmri.Manager.LOGIXNG_DIGITAL_BOOLEAN_ACTIONS);
}
InstanceManager.setDefault(DigitalBooleanActionManager.class, m6);
DigitalExpressionManager m7 = new DefaultDigitalExpressionManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m7, jmri.Manager.LOGIXNG_DIGITAL_EXPRESSIONS);
}
InstanceManager.setDefault(DigitalExpressionManager.class, m7);
StringActionManager m8 = new DefaultStringActionManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m8, jmri.Manager.LOGIXNG_STRING_ACTIONS);
}
InstanceManager.setDefault(StringActionManager.class, m8);
StringExpressionManager m9 = new DefaultStringExpressionManager();
if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
InstanceManager.getDefault(ConfigureManager.class).registerConfig(m9, jmri.Manager.LOGIXNG_STRING_EXPRESSIONS);
}
InstanceManager.setDefault(StringExpressionManager.class, m9);
jmri.jmrit.logixng.NamedBeanType.reset();
jmri.jmrit.logixng.actions.CommonManager.reset();
if (activate) m1.activateAllLogixNGs(false, false);
}
public static void initInternalSensorManagerThrowException() {
InstanceManager.setDefault(SensorManager.class, new SensorManagerThrowExceptionScaffold());
}
public static void initLightManagerThrowException() {
InstanceManager.setDefault(LightManager.class, new InternalLightManagerThrowExceptionScaffold());
}
public static void initMemoryManagerThrowException() {
InstanceManager.setDefault(MemoryManager.class, new MemoryManagerThrowExceptionScaffold());
}
public static void initSignalHeadManagerThrowException() {
InstanceManager.setDefault(SignalHeadManager.class, new SignalHeadManagerThrowExceptionScaffold());
}
public static void initSignalMastManagerThrowException() {
InstanceManager.setDefault(SignalMastManager.class, new SignalMastManagerThrowExceptionScaffold());
}
public static void initWarrantManagerThrowException() {
InstanceManager.setDefault(WarrantManager.class, new WarrantManagerThrowExceptionScaffold());
}
public static void initOBlockManagerThrowException() {
InstanceManager.setDefault(OBlockManager.class, new OBlockManagerThrowExceptionScaffold());
}
public static void initRouteManagerThrowException() {
InstanceManager.setDefault(RouteManager.class, new RouteManagerThrowExceptionScaffold());
}
/**
* Initialize a {@link jmri.util.zeroconf.MockZeroConfServiceManager} after
* ensuring that any existing
* {@link jmri.util.zeroconf.ZeroConfServiceManager} (real or mocked) has
* stopped all services it is managing.
*/
public static void initZeroConfServiceManager() {
Assertions.assertTrue(JUnitUtil.resetZeroConfServiceManager());
InstanceManager.setDefault(ZeroConfServiceManager.class, new MockZeroConfServiceManager());
}
/**
* Ensure that any existing
* {@link jmri.util.zeroconf.ZeroConfServiceManager} (real or mocked) has
* stopped all services it is managing.
* @return true when complete.
*/
@CheckReturnValue
public static boolean resetZeroConfServiceManager() {
if (! InstanceManager.containsDefault(ZeroConfServiceManager.class)) {
return true; // not present, don't create one by asking for it.
}
ZeroConfServiceManager manager = InstanceManager.getDefault(ZeroConfServiceManager.class);
manager.stopAll();
waitFor( () -> manager.allServices().isEmpty(), "Stopping all ZeroConf Services");
manager.dispose();
var threads = Thread.getAllStackTraces().keySet();
for (Thread t : threads) {
if (t.getName().startsWith(ZeroConfServiceManager.DNS_CLOSE_THREAD_NAME)) {
waitThreadTerminated(t);
}
}
return true;
}
/**
* End any running BlockBossLogic (Simple Signal Logic) objects
*/
public static void clearBlockBossLogic() {
if(InstanceManager.containsDefault(BlockBossLogicProvider.class)) {
InstanceManager.getDefault(BlockBossLogicProvider.class).dispose();
}
}
/**
* Leaves ShutDownManager, if any, in place,
* but removes its contents.
*
* Instead of using this,
* it's better to have your test code remove _and_ _check_
* for specific items; this just suppresses output from the
* {@link #checkShutDownManager()} check down as part of the
* default end-of-test code.
*
* @see #checkShutDownManager()
*/
public static void clearShutDownManager() {
if (! InstanceManager.containsDefault(ShutDownManager.class)) return; // not present, stop (don't create)
ShutDownManager sm = InstanceManager.getDefault(jmri.ShutDownManager.class);
List
* The new profile will have the name {@literal TestProfile}, the id
* {@literal 00000000}, and will be in the directory {@literal temp}
* within the sources working copy.
*/
public static void resetProfileManager() {
try {
Profile profile = new NullProfile("TestProfile", "00000000", FileUtil.getFile(FileUtil.SETTINGS));
resetProfileManager(profile);
} catch (FileNotFoundException ex) {
log.error("Settings directory \"{}\" does not exist", FileUtil.SETTINGS);
} catch (IOException | IllegalArgumentException ex) {
log.error("Unable to create profile", ex);
}
}
/**
* Use when an isolated per-test profile directory is available (e.g. from
* {@literal @TempDir}). Guarantees a clean profile regardless of the host
* machine's JMRI settings directory.
*
* @param tempDir a writable directory for the profile, typically from {@literal @TempDir}
*/
public static void resetProfileManager(File tempDir) {
try {
resetProfileManager(new NullProfile(tempDir));
} catch (IOException ex) {
log.error("Unable to create profile in {}", tempDir, ex);
}
}
/**
* Use if the profile needs to be written to or cleared as part of the test.
* A temporary folder is suggested for the profile, see
* https://www.jmri.org/help/en/html/doc/Technical/JUnit.shtml#tempFileCreation
*
* First implementation is rather simplistic.
*/
static void handleThreads() {
// now check for extra threads
ThreadGroup main = Thread.currentThread().getThreadGroup();
while (main.getParent() != null ) {main = main.getParent(); }
Thread[] list = new Thread[main.activeCount()+2]; // space on end
int max = main.enumerate(list);
for (int i = 0; iJUnitUtil.waitFor(()->{return replyVariable != null;},"reply not received");
*
* @param condition condition being waited for
* @param name name of condition being waited for; will appear in
* Assertions.fail if condition not true fast enough
*/
public static void waitFor( @Nonnull ReleaseUntil condition, @Nonnull String name) {
waitFor( condition, () -> name);
}
/**
* Wait for a specific condition to be true, without having to wait longer.
* JUnitUtil.waitFor( () -> { return replyVariable != null; },
* () -> "replyVariable still null: " + computationallyExpensiveCall() + " or multiple Strings" );
* @param condition condition being waited for.
* @param messageSupplier Failure text supplier.
*/
@SuppressFBWarnings("REC_CATCH_EXCEPTION")
public static void waitFor( @Nonnull ReleaseUntil condition , @Nonnull SupplierAssumptions.assumeTrue("reply not received", JUnitUtil.waitFor(()->{return replyVariable != null;}));
*
* @param condition condition to wait for
* @return true if condition is met before WAITFOR_MAX_DELAY, false
* otherwise
*/
@CheckReturnValue
public static boolean waitFor(ReleaseUntil condition) {
if (javax.swing.SwingUtilities.isEventDispatchThread()) {
log.error("Cannot use waitFor on Swing thread", new Exception());
return false;
}
int delay = 0;
try {
while (delay < WAITFOR_MAX_DELAY) {
if (condition.ready()) {
return true;
}
int priority = Thread.currentThread().getPriority();
try {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
Thread.sleep(WAITFOR_DELAY_STEP);
delay += WAITFOR_DELAY_STEP;
} catch (InterruptedException e) {
return false;
} finally {
Thread.currentThread().setPriority(priority);
}
}
return false;
} catch (Exception ex) {
log.error("Exception in waitFor condition.", ex);
return false;
}
}
/**
* Wait for a specific amount of time
* JUnitUtil.fasterWaitFor(()->{return replyVariable != null;},"reply not received")
*
* @param condition condition being waited for
* @param name name of condition being waited for; will appear in
* Assertions.fail if condition not true fast enough
*/
@SuppressFBWarnings("REC_CATCH_EXCEPTION")
public static void fasterWaitFor(ReleaseUntil condition, String name) {
if (javax.swing.SwingUtilities.isEventDispatchThread()) {
log.error("Cannot use waitFor on Swing thread", new Exception());
return;
}
int delay = 0;
try {
while (delay < 1000) {
try {
if (condition.ready()) {
return;
}
} catch(Exception ex) {
Assertions.fail("Exception while processing condition for \"" + name + "\" ", ex);
}
int priority = Thread.currentThread().getPriority();
try {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
Thread.sleep(5);
delay += 5;
} catch (InterruptedException e) {
Assertions.fail("failed due to InterruptedException", e);
} finally {
Thread.currentThread().setPriority(priority);
}
}
Assertions.fail("\"" + name + "\" did not occur in time");
} catch (Exception ex) {
Assertions.fail("Exception while waiting for \"" + name + "\" ", ex);
}
}
/**
* Wait at most 1 second for a specific condition to be true, without having to wait longer
* Assume.assumeTrue("reply not received", JUnitUtil.fasterWaitForTrue(()->{return replyVariable != null;}));
*
* @param condition condition to wait for
* @return true if condition is met before 1 second, false
* otherwise
*/
@CheckReturnValue
public static boolean fasterWaitFor(ReleaseUntil condition) {
if (javax.swing.SwingUtilities.isEventDispatchThread()) {
log.error("Cannot use waitFor on Swing thread", new Exception());
return false;
}
int delay = 0;
try {
while (delay < 1000) {
if (condition.ready()) {
return true;
}
int priority = Thread.currentThread().getPriority();
try {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
Thread.sleep(5);
delay += 5;
} catch (InterruptedException e) {
return false;
} finally {
Thread.currentThread().setPriority(priority);
}
}
return false;
} catch (Exception ex) {
log.error("Exception in waitFor condition.", ex);
return false;
}
}
/**
* Reset the user files path in the default
* {@link jmri.util.FileUtilSupport} object (used by
* {@link jmri.util.FileUtil}) to the default settings/user files path for
* tests of {@code git-working-copy/temp}.
*/
public static void resetFileUtilSupport() {
FileUtilSupport.resetInstance();
FileUtilSupport.getDefault().setUserFilesPath(
ProfileManager.getDefault().getActiveProfile(), FileUtil.getPreferencesPath());
}
public static interface ReleaseUntil {
public boolean ready() throws Exception;
}
/**
* Set a NamedBean (Turnout, Sensor, SignalHead, ...) to a specific value in
* a thread-safe way.
*
* jmri.profile.Profile profile = new jmri.profile.NullProfile(temporaryFolder);
* JUnitUtil.resetProfileManager(profile);
*
*
* @param profile the provided profile
*/
public static void resetProfileManager(Profile profile) {
ProfileManager.getDefault().setActiveProfile(profile);
InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).setEnableStoreCheck(false);
}
/**
* PreferencesProviders retain per-profile objects; reset them to force that
* information to be dumped.
*/
public static void resetPreferencesProviders() {
try {
// reset UI provider
Field providers = JmriUserInterfaceConfigurationProvider.class.getDeclaredField("PROVIDERS");
providers.setAccessible(true);
((Map, ?>) providers.get(null)).clear();
// reset XML storage provider
providers = JmriConfigurationProvider.class.getDeclaredField("PROVIDERS");
providers.setAccessible(true);
((Map, ?>) providers.get(null)).clear();
// reset java.util.prefs.Preferences storage provider
Field shared = JmriPreferencesProvider.class.getDeclaredField("SHARED_PROVIDERS");
Field privat = JmriPreferencesProvider.class.getDeclaredField("PRIVATE_PROVIDERS");
shared.setAccessible(true);
((Map, ?>) shared.get(null)).clear();
privat.setAccessible(true);
((Map, ?>) privat.get(null)).clear();
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
log.error("Unable to reset preferences providers", ex);
}
}
/**
* Silences the outputs from the Jemmy GUI Test framework.
*/
public static void silenceGUITestOutput() {
JUnitUtil.setGUITestOutput(TestOut.getNullOutput());
}
/**
* Sets the outputs for the Jemmy GUI Test framework to the defaults. Call
* this after setting up logging to enable outputs for a specific test.
*/
public static void verboseGUITestOutput() {
JUnitUtil.setGUITestOutput(new TestOut());
}
/**
* Set the outputs for the Jemmy GUI Test framework.
*
* @param output a container for the input, output, and error streams
*/
public static void setGUITestOutput(TestOut output) {
org.netbeans.jemmy.JemmyProperties.setCurrentOutput(output);
}
/**
* Service method to find the test class name in the traceback. Heuristic:
* First jmri or apps class that isn't this one.
* @return String class name
*/
static String getTestClassName() {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
for (StackTraceElement e : trace) {
String name = e.getClassName();
if (name.startsWith("jmri") || name.startsWith("apps")) {
if (!name.endsWith("JUnitUtil")) {
return name;
}
}
}
return "