package jmri.jmrit.logixng; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.swing.JDialog; import javax.swing.JPanel; import jmri.Category; import jmri.InstanceManager; import jmri.jmrit.logixng.swing.SwingConfiguratorInterface; import jmri.util.JUnitUtil; // import org.apache.log4j.Level; import org.junit.jupiter.api.*; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test that all the action and expression classes are registered and have * configurexml and swing classes. *

* Requirements that this class checks for: * * Each action and expression needs to * * be registered in its manager * * have a configurexml class * * have a swing configurator class * * have a test class for its swing configurator class * * @author Daniel Bergqvist 2020 */ public class ActionsAndExpressionsTest { private static final Locale DEFAULT_LOCALE = Locale.getDefault(); private boolean errorsFound = false; private final Set languages = new HashSet<>(); private final Set locales = new HashSet<>(); // Populates languages and locales fields. Called in test setup. private void getLocales() throws IOException { Path path = FileSystems.getDefault().getPath("java/src/jmri/jmrit/logixng/"); Files.walk(path) .filter(Files::isRegularFile) .filter(p -> p.getFileName().toString().endsWith(".properties")) .filter(p -> p.getFileName().toString().contains("_")) .forEach(p -> { String file = p.getFileName().toString(); languages.add(file.substring(file.indexOf('_')+1, file.length()-".properties".length())); }); for (String lang : languages) { // System.out.format("Language: '%s'%n", lang); locales.add(new Locale(lang)); } } private void checkFolder(Path path, String packageName, Map>> registeredClasses, String[] classesToIgnore) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, java.lang.reflect.InvocationTargetException { JDialog dialog = new JDialog(); Set files = Stream.of(path.toFile().listFiles()) .filter(file -> !file.isDirectory()) .map(File::getName) .collect(Collectors.toSet()); filesLoop: for (String file : files) { if (file.endsWith(".properties")) { continue; } String firstPartOfFileName = file.substring(0, file.indexOf('.')); for (String c : classesToIgnore) { if (firstPartOfFileName.equals(c)) { continue filesLoop; } } // Check that all actions and expressions is registered in its manager Set> setOfClasses = new HashSet<>(); boolean isRegistered = false; for (Map.Entry>> entry : registeredClasses.entrySet()) { for (Class c : entry.getValue()) { // System.out.format("Registered class: %s%n", c.getName()); if (c.getName().equals(packageName+"."+firstPartOfFileName)) { isRegistered = true; } assertFalse( setOfClasses.contains(c), () -> String.format("Class %s is registered more than once in the manager", packageName+"."+c.getName())); setOfClasses.add(c); // if (setOfClasses.contains(c)) { // System.out.format("Class %s is registered more than once in the manager", packageName+"."+c.getName()); // errorsFound = true; // } else { // setOfClasses.add(c); // } } } // if (!isRegistered) { // System.out.format("Class %s.%s is not registered in the manager%n", packageName, firstPartOfFileName); // errorsFound = true; // } assertTrue( isRegistered, String.format("Class %s is registred%n", firstPartOfFileName)); String fullConfigName; // Ignore this for now // Check that all actions and expressions has a xml class Object configureXml = null; fullConfigName = packageName + ".configurexml." + firstPartOfFileName + "Xml"; log.debug("getAdapter looks for {}", fullConfigName); try { Class configClass = Class.forName(fullConfigName); configureXml = configClass.getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | java.lang.reflect.InvocationTargetException e) { } if (configureXml == null) { System.out.format("Class %s.%s has no configurexml class%n", packageName, firstPartOfFileName); errorsFound = true; } // Disable for now // Assert.assertNotNull(String.format("Class %s has xml class%n", firstPartOfFileName), configureXml); // Check that all actions and expressions has a swing class SwingConfiguratorInterface configureSwing; fullConfigName = packageName + ".swing." + firstPartOfFileName + "Swing"; log.debug("getAdapter looks for {}", fullConfigName); Class configClass = Class.forName(fullConfigName); configureSwing = (SwingConfiguratorInterface)configClass.getDeclaredConstructor().newInstance(); configureSwing.setJDialog(dialog); configureSwing.getConfigPanel(new JPanel()); MaleSocket socket = configureSwing.createNewObject(configureSwing.getAutoSystemName(), null); MaleSocket lastMaleSocket = socket; assertNotNull(lastMaleSocket); Base base = socket; assertNotNull(base); while ((base instanceof MaleSocket)) { lastMaleSocket = (MaleSocket) base; base = ((MaleSocket)base).getObject(); } assertNotNull(base); assertEquals( base.getClass().getName(), packageName+"."+firstPartOfFileName, "SwingConfiguratorInterface creates an object of correct type"); // System.out.format("Swing: %s, Class: %s, class: %s%n", configureSwing.toString(), socket.getShortDescription(), socket.getObject().getClass().getName()); assertEquals( socket.getShortDescription(), configureSwing.toString(), "Swing class has correct name"); // System.out.format("MaleSocket class: %s, socket class: %s%n", // configureSwing.getManager().getMaleSocketClass().getName(), // socket.getClass().getName()); assertTrue(configureSwing.getManager().getMaleSocketClass().isAssignableFrom(lastMaleSocket.getClass())); // Test all locales. This mainly tests that the female socket // names are valid for each locale, for example that the name // doesn't contain any spaces. for (Locale locale : locales) { Locale.setDefault(locale); configureSwing.createNewObject(configureSwing.getAutoSystemName(), null); } Locale.setDefault(DEFAULT_LOCALE); // if (configureSwing == null) { // System.out.format("Class %s.%s has no swing class%n", packageName, firstPartOfFileName); // errorsFound = true; // } assertNotNull( configureSwing, String.format("Class %s has swing class%n", firstPartOfFileName)); // Ignore for now /* // Check that all actions and expressions has a test class for the swing class // Class configureSwingTest = null; fullConfigName = packageName + ".swing." + firstPartOfFileName + "SwingTest"; log.debug("getAdapter looks for {}", fullConfigName); Class configClass = null; try { configClass = Class.forName(fullConfigName); } catch (ClassNotFoundException e) { } if (configClass == null) { System.out.format("Class %s.%s has no test class for its swing class%n", packageName, firstPartOfFile); errorsFound = true; } // Assert.assertNotNull("The swing class has a test class", configClass); */ /* System.out.format("Class: %s%n", packageName+"."+firstPartOfFileName); Level severity = Level.ERROR; // level at or above which we'll complain boolean unexpectedMessageSeen = JUnitAppender.unexpectedMessageSeen(severity); String unexpectedMessageContent = JUnitAppender.unexpectedMessageContent(severity); JUnitAppender.verifyNoBacklog(); JUnitAppender.resetUnexpectedMessageFlags(severity); Assert.assertFalse("Unexpected "+severity+" or higher messages emitted: "+unexpectedMessageContent, unexpectedMessageSeen); // JUnitAppender.assertNoErrorMessage(); */ } } private Path getPath(String subFolder) { return FileSystems.getDefault().getPath("java/src/jmri/jmrit/logixng/" + subFolder); } public Map>> getAnalogActionClasses() { return InstanceManager.getDefault(AnalogActionManager.class).getActionClasses(); } public Map>> getAnalogExpressionClasses() { return InstanceManager.getDefault(AnalogExpressionManager.class).getExpressionClasses(); } public Map>> getDigitalActionClasses() { return InstanceManager.getDefault(DigitalActionManager.class).getActionClasses(); } public Map>> getDigitalBooleanActionClasses() { return InstanceManager.getDefault(DigitalBooleanActionManager.class).getActionClasses(); } public Map>> getDigitalExpressionClasses() { return InstanceManager.getDefault(DigitalExpressionManager.class).getExpressionClasses(); } public Map>> getStringActionClasses() { return InstanceManager.getDefault(StringActionManager.class).getActionClasses(); } public Map>> getStringExpressionClasses() { return InstanceManager.getDefault(StringExpressionManager.class).getExpressionClasses(); } public void addClasses(Map>> classes, Map>> newClasses) { newClasses.entrySet().forEach((entry) -> { // System.out.format("Add action: %s, %s%n", entry.getKey().name(), entry.getValue().getName()); entry.getValue().forEach((clazz) -> { classes.get(entry.getKey()).add(clazz); }); }); } @Test @DisabledIfSystemProperty(named = "java.awt.headless", matches = "true") public void testGetBeanType() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, java.lang.reflect.InvocationTargetException { Map>> classes = new HashMap<>(); for (Category category : Category.values()) { classes.put(category, new ArrayList<>()); } addClasses(classes, getAnalogActionClasses()); addClasses(classes, getDigitalActionClasses()); addClasses(classes, getDigitalBooleanActionClasses()); addClasses(classes, getStringActionClasses()); checkFolder( getPath("actions"), "jmri.jmrit.logixng.actions", classes, new String[]{ "Bundle", "CommonManager", "NamedBeanType", "AbstractAnalogAction","AnalogFactory", // Analog "AbstractDigitalAction","ActionAtomicBoolean","AbstractScriptDigitalAction","DigitalFactory", // Digital "AbstractDigitalBooleanAction","DigitalBooleanFactory", // Boolean digital "AbstractStringAction","StringFactory", // String "SetLocalVariables","RunFinally" }); classes = new HashMap<>(); for (Category category : Category.values()) { classes.put(category, new ArrayList<>()); } addClasses(classes, getAnalogExpressionClasses()); addClasses(classes, getDigitalExpressionClasses()); addClasses(classes, getStringExpressionClasses()); checkFolder( getPath("expressions"), "jmri.jmrit.logixng.expressions", classes, new String[]{ "Bundle", "AbstractAnalogExpression","AnalogFactory", // Analog "AbstractDigitalExpression","AbstractScriptDigitalExpression","DigitalFactory", // Digital "AbstractStringExpression","StringFactory", // String "ExpressionLinuxLinePower" // Only exists on Linux }); assertFalse( errorsFound, "No errors found"); } // The minimal setup for log4J @BeforeEach public void setUp() throws IOException { JUnitUtil.setUp(); JUnitUtil.resetInstanceManager(); JUnitUtil.resetProfileManager(); JUnitUtil.initConfigureManager(); JUnitUtil.initInternalSensorManager(); JUnitUtil.initInternalTurnoutManager(); JUnitUtil.initDebugThrottleManager(); JUnitUtil.initLogixNGManager(); // Get the list of languages LogixNG has been translated to getLocales(); // Temporary let the error messages from this test be shown to the user // JUnitAppender.end(); } @AfterEach public void tearDown() { jmri.jmrit.logixng.util.LogixNG_Thread.stopAllLogixNGThreads(); JUnitUtil.deregisterBlockManagerShutdownTask(); JUnitUtil.tearDown(); } private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionsAndExpressionsTest.class); }