Files
2026-06-17 14:00:51 +02:00

258 lines
10 KiB
Java

package jmri.jmrit;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
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.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.*;
import jmri.util.FileUtil;
import jmri.util.JUnitUtil;
import org.jdom2.*;
import org.jdom2.input.SAXBuilder;
import org.junit.jupiter.api.*;
/**
* Tests for the XmlFile class.
* <p>
* Uses (creates, modifies, destroys) files in the local preferences directory
* and the custom <user.home>/temp/xml directory
*
* @author Bob Jacobsen Copyright 2001
*/
public class XmlFileTest {
// file urls are relative to the
// program directory
static final String testFileDir = "java" + File.separator
+ "test" + File.separator
+ "jmri" + File.separator
+ "util" + File.separator
+ "xml" + File.separator;
// Test cases to make sure schema and DTD validation
// is properly controlled. For each of DTD valid, invalid and absent
// (ditto schema), check with validate on and off that proper result is obtained.
// That's 3*3*2*2 cases!
enum Type { ABSENT, VALID, INVALID }
@Test
public void testValidationControl() {
final String docTypeValid = "<!DOCTYPE decoderIndex-config SYSTEM \"decoderIndex-config.dtd\">";
final String docTypeInvalid = "<!DOCTYPE layout-config SYSTEM \"layout-config-2-5-4.dtd\">";
final String contentSchemaValid = "<decoderIndex-config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://jmri.org/xml/schema/decoder.xsd\">";
final String contentSchemaInvalid = "<decoderIndex-config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://jmri.org/xml/schema/layout-2-9-6.xsd\">";
final String contentSchemaAbsent = "<decoderIndex-config>";
final String ending = "<decoderIndex><mfgList nmraListDate=\"\" updated=\"\"><manufacturer mfg=\"\"/></mfgList><familyList><family mfg=\"\" name=\"\" file=\"\"/></familyList></decoderIndex></decoderIndex-config>";
for (XmlFile.Validate validate : XmlFile.Validate.values()) {
for (Type theDTD : Type.values()) {
for (Type theSchema : Type.values()) {
boolean passes = true;
boolean checkDTD = (validate == XmlFile.Validate.CheckDtd) || (validate == XmlFile.Validate.CheckDtdThenSchema);
boolean checkSchema = (validate == XmlFile.Validate.RequireSchema) || (validate == XmlFile.Validate.CheckDtdThenSchema);
if (theSchema == Type.INVALID && checkSchema) passes = false; // Cannot find the declaration of element 'decoderIndex-config'.
if (theSchema == Type.ABSENT && checkSchema) passes = false; // Cannot find the declaration of element 'decoderIndex-config'.
if (theDTD == Type.INVALID && checkDTD) passes = false; // Document root element "decoderIndex-config", must match DOCTYPE root "layout-config".
// but if you're checking both
if ( checkDTD && checkSchema) {
// a pass is a pass
if (theDTD == Type.VALID) passes = true;
if (theSchema == Type.VALID) passes = true;
// but a DTD fail is a fail
if (theDTD == Type.INVALID) passes = false;
}
// create input
String content = "";
if (theDTD == Type.VALID) content += docTypeValid;
if (theDTD == Type.INVALID) content += docTypeInvalid;
if (theSchema == Type.VALID) content += contentSchemaValid;
if (theSchema == Type.INVALID) content += contentSchemaInvalid;
if (theSchema == Type.ABSENT) content += contentSchemaAbsent;
content += ending;
boolean result;
try {
XmlFile xf = new XmlFile() {}; // odd syntax is due to XmlFile being abstract
xf.setValidate(validate);
xf.rootFromInputStream(new java.io.ByteArrayInputStream(content.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
result = true;
} catch (IOException | JDOMException ex) {
result = false;
log.debug("result false: ", ex);
}
log.debug("DTD: {} SCHEMA: {} ({}) expects {} was {}{}", theDTD, theSchema, validate, passes, result, passes != result ? " !!!!!!!!!!!!!!!!!!!!!!!!!" : "");
assertEquals( passes, result,
() -> "DTD: "+theDTD+" SCHEMA: "+theSchema+" ("+validate+")");
}
}
}
}
@Test
public void testProgIncludeRelative() {
validateFileAndDtdAccess(new File(testFileDir + "ProgramMainRelative.xml"));
}
@Test
public void testProgIncludeURL() {
validateFileAndDtdAccess(new File(testFileDir + "ProgramMainURL.xml"));
}
@Test
public void testDotDotDTD() {
validateFileAndDtdAccess(new File(testFileDir + "DotDotDTD.xml"));
}
@Test
public void testHttpURL() {
validateFileAndDtdAccess(new File(testFileDir + "HttpURL.xml"));
}
@Test
public void testJustFilename() {
validateFileAndDtdAccess(new File(testFileDir + "JustFilename.xml"));
}
@Test
public void testPathname() {
validateFileAndDtdAccess(new File(testFileDir + "Pathname.xml"));
}
private void validateFileAndDtdAccess(File file) { // don't check Schema so can check DTD
assertDoesNotThrow( () -> {
XmlFile xf = new XmlFile() {
}; // odd syntax is due to XmlFile being abstract
xf.setValidate(XmlFile.Validate.CheckDtd);
xf.rootFromFile(file);
});
}
@Test
public void testCheckFile() {
// XmlFile is abstract, so can't check ctor directly; use local class
XmlFile x = new XmlFile() {
};
// this test uses explicit filenames intentionally, to ensure that
// the resulting files go into the test tree area. This is not
// a test of the user's files directory, and shouldn't use that.
FileUtil.createDirectory("temp" + File.separator + "prefs");
assertTrue( x.checkFile("decoders"), "existing file "); // should be in xml
assertFalse( x.checkFile("dummy file not expected to exist"), "non-existing file ");
}
@Test
public void testNotVoid() throws JDOMException, IOException {
// XmlFile is abstract, so can't check ctor directly; use local class
XmlFile x = new XmlFile() {
};
// get Element from non-existant file
FileNotFoundException e = assertThrows( FileNotFoundException.class,
() -> x.rootFromFile(new File("nothingwerelikelytofind.xml")));
assertNotNull(e, "Throws if file not found");
}
@Test
public void testWriteFile() throws IOException {
XmlFile x = new XmlFile() {
};
// create a minimal XML file
Element root = new Element("decoder-config");
root.setAttribute("noNamespaceSchemaLocation",
"http://jmri.org/xml/schema/decoder.xsd",
org.jdom2.Namespace.getNamespace("xsi",
"http://www.w3.org/2001/XMLSchema-instance"));
Document doc = new Document(root);
doc.setDocType(new DocType("decoder-config", "decoder-config.dtd"));
// write it out
FileUtil.createDirectory("temp" + File.separator + "prefs");
File f = new File("temp" + File.separator + "prefs" + File.separator + "test.xml");
x.writeXML(f, doc);
assertTrue( f.exists(), "File expected to be present");
}
@Test
public void testReadFile() throws JDOMException, IOException {
// ensure file present
testWriteFile();
// try to read
XmlFile x = new XmlFile() {
};
// not a real file
x.setValidate(XmlFile.Validate.None);
Element e = x.rootFromName("temp" + File.separator + "prefs" + File.separator + "test.xml");
assertNotNull( e, "Element found");
}
@Test
public void testProcessPI() throws JDOMException, IOException {
// Document from test file
Document doc;
Element e;
try (FileInputStream fs = new FileInputStream(new File("java/test/jmri/jmrit/XmlFileTest_PI.xml"))) {
SAXBuilder builder = XmlFile.getBuilder(XmlFile.Validate.None); // argument controls validation
doc = builder.build(new BufferedInputStream(fs));
assertNotNull( doc, "Original Document found");
e = doc.getRootElement();
assertNotNull( e, "Original root element found");
XmlFile x = new XmlFile() {
};
Document d = x.processInstructions(doc);
assertNotNull(d);
// test transform changes <contains> element to <content>
e = d.getRootElement();
assertNotNull( e, "Transformed root element found");
assertEquals( "top", e.getName(), "Transformed root element is right type");
assertNull( e.getChild("contains"), "Old element gone");
assertNotNull( e.getChild("content"), "New element there");
assertEquals( 2, e.getChild("content").getChildren().size(), "New element has content");
}
}
@BeforeEach
public void setUp() {
JUnitUtil.setUp();
}
@AfterEach
public void tearDown() {
JUnitUtil.tearDown();
}
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XmlFileTest.class);
}