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 extends Base> 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);
}