/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* XMLW3CConformanceTestSuite.java
* Creation date: September 2007.
* By: Malcolm Sharpe
*/
package org.openquark.cal.foreignsupport.module.Engine_Tests;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import javax.xml.parsers.*;
import org.openquark.cal.services.BasicCALServices;
import org.openquark.cal.services.CALServicesTestUtilities;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.io.EntryPointSpec;
import org.w3c.dom.*;
import junit.framework.*;
/**
* A test harness for running the XML W3C Conformance Test Suite on
* the module Cal.Experimental.Utilities.XmlParser.Engine.<p>
*
* To run the conformance tests, first download the test suite, and then run this
* class with the property org.openquark.cal.foreignsupport.module.Engine_Tests.xmlconfPath
* set to the path to xmlconf.xml, which is part of the test suite.<p>
*
* This class is designed for the 10 December 2003 version of the test suite, and as
* such contains a few hard-coded fixes for minor bugs in that version of the suite.
* This is particularly needed for tests which involve reading external entities, since
* they are fairly often mislabelled.<p>
*
* @see <a href="http://www.w3.org/XML/Test/">XML W3C Conformance Test Suite</a>
*
* @author Malcolm Sharpe
*/
public class XMLW3CConformanceTestSuite {
private static final String XMLCONF_PATH_PROP = "org.openquark.cal.foreignsupport.module.Engine_Tests.xmlconfPath";
/** The path to xmlconf.xml. */
private static final String XMLCONF_PATH = System.getProperty(XMLCONF_PATH_PROP);
/**
* Create a test suite by parsing the XML test description file, "xmlconf.xml".
* Its path is given by {@link #XMLCONF_PATH}.
*
* For details, see {@link #recursivelyCreateTest}.
*
* @return a test suite automatically generated from "xmlconf.xml".
* @throws Exception
*/
public static Test suite() throws Exception {
if (XMLCONF_PATH == null) throw new NullPointerException(XMLCONF_PATH_PROP+" property must be set when running the conformance suite");
// Parse "xmlconf.xml".
URI baseUri = new File(XMLCONF_PATH).getParentFile().toURI();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(XMLCONF_PATH);
// Recursively create the suite.
return recursivelyCreateTest(doc.getDocumentElement(), baseUri);
}
/**
* Generate a test from the test descriptions descended from the given element.
* If the element is a TESTSUITE or TESTCASES element, a {@link TestSuite} is
* created with a name corresponding to the element's PROFILE attribute. If the
* element is a TEST element, an {@link XMLParserTest} is created, which handles
* the execution of the test.
*
* @param testElement the element from which to generate a test or test suite.
* @param baseUri the physical location of testElement's parent.
* @return the test or test suite generated from the element.
* @throws URISyntaxException
*/
private static Test recursivelyCreateTest(Element testElement, URI baseUri) throws URISyntaxException {
if (!testElement.getTagName().equals("TEST")) {
// The element is either TESTSUITE or TESTCASES, but they can
// be treated uniformly.
String profile = testElement.getAttribute("PROFILE");
String xmlBase = testElement.getAttribute("xml:base");
// Unfortunately, the test description files seem to misuse xml:base.
// To work around it, append a '/' if both: there isn't one already
// and this is a relative URI.
if (!new URI(xmlBase).isAbsolute() && xmlBase.length() > 0 && xmlBase.charAt(xmlBase.length() - 1) != '/') {
xmlBase += '/';
}
NodeList children = testElement.getChildNodes();
TestSuite suite = new TestSuite(profile);
// Traverse each element child.
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (!(child instanceof Element)) continue;
Element childElement = (Element)child;
// Only use TEST elements for XML 1.0.
if (childElement.hasAttribute("RECOMMENDATION") &&
!"XML1.0".equals(childElement.getAttribute("RECOMMENDATION"))) continue;
// Optional error test cases are ignored.
if ("error".equals(childElement.getAttribute("TYPE"))) continue;
suite.addTest(recursivelyCreateTest(childElement, baseUri.resolve(xmlBase)));
}
return suite;
} else {
fixTestBugs(testElement);
// The element is TEST, so create the test.
URI relUri = new URI(testElement.getAttribute("URI"));
URI uri = baseUri.resolve(relUri);
String path = new File(uri).getPath();
return new XMLParserTest(testElement, path, baseUri);
}
}
/**
* Hardcoded fixes for minor bugs in the 10 December 2003 version of
* the XML W3C Conformance Test Suite. These bugs are almost all mislabelling
* of entity use. That only matters for non-validating parsers that do not read
* external entities, which are rare, so that is likely why they have not been
* fixed yet. The one other bug is an output test that does not conform to the
* canonical XML form, so we ignore its output.
*
* @param testElement the test element to fix.
*/
private static void fixTestBugs(Element testElement) {
String uri = testElement.getAttribute("URI");
if ("not-wf/P32/ibm32n09.xml".equals(uri)) {
testElement.setAttribute("ENTITIES", "none");
} else if ("not-wf/P68/ibm68n06.xml".equals(uri)) {
testElement.setAttribute("ENTITIES", "none");
} else if ("not-wf/not-sa03.xml".equals(uri)) {
testElement.setAttribute("ENTITIES", "none");
} else if ("not-wf/sa/081.xml".equals(uri)) {
testElement.setAttribute("ENTITIES", "none");
} else if ("not-wf/sa/082.xml".equals(uri)) {
testElement.setAttribute("ENTITIES", "none");
} else if ("not-wf/sa/185.xml".equals(uri)) {
testElement.setAttribute("ENTITIES", "none");
} else if ("not-wf/not-sa/002.xml".equals(uri)) {
testElement.setAttribute("ENTITIES", "none");
} else if ("valid/P29/ibm29v01.xml".equals(uri)) {
testElement.removeAttribute("OUTPUT");
}
}
}
/**
* A test of the XML parser on a single input file.
*
* @author Malcolm Sharpe
*/
class XMLParserTest extends TestCase {
public XMLParserTest(Element testElement, String path, URI baseUri) {
super(testElement.getAttribute("ID") +
" -- " +
(("none".equals(testElement.getAttribute("ENTITIES")) ||
!"not-wf".equals(testElement.getAttribute("TYPE"))) ? testElement.getAttribute("TYPE") : "invalid") +
" -- " +
testElement.getAttribute("SECTIONS"));
this.testElement = testElement;
this.path = path;
this.baseUri = baseUri;
}
private Element testElement;
private String path;
private URI baseUri;
/**
* Run the XML parser on a single input file, and determine whether the
* parser correctly identified its input as well-formed or not well-formed.
* In addition, if there is output, ensure that the parser's canonicalized
* output matches it.
*/
public void runTest() throws Exception {
String type = testElement.getAttribute("TYPE");
String entities = testElement.getAttribute("ENTITIES");
File tempFile = File.createTempFile("output", ".xml");
tempFile.deleteOnExit();
if ("not-wf".equals(type) && "none".equals(entities)) {
// We expect isWellFormed to return false.
boolean iswf = isWellFormed(path);
assertFalse("document should not be considered well-formed", iswf);
} else if ("invalid".equals(type) || "valid".equals(type) || "not-wf".equals(type)) {
// We expect isWellFormed to return true. In the case of not-wf, this is
// because we do not read external entities.
// Furthermore, there may be an OUTPUT or OUTPUT3 attribute
// associated with this test.
// If this test references external entities, there's a good chance that we
// can't write useful output, since the attribute defaults we see will be
// different than what a validating parser sees.
boolean canWriteUsefulOutput = "none".equals(entities);
String referenceOutputPath = null;
if (canWriteUsefulOutput && testElement.hasAttribute("OUTPUT")) {
parseAndWriteSecondXmlCanonicalForm(path, tempFile.getPath());
referenceOutputPath = testElement.getAttribute("OUTPUT");
} else if (canWriteUsefulOutput && testElement.hasAttribute("OUTPUT3")) {
// At the time of writing, no tests use the OUTPUT3 attribute, so
// there is no need to support the Third XML Canonical Form.
throw new Exception("Third XML Canonical Form not supported");
} else {
assertTrue("document should be considered well-formed", isWellFormed(path));
}
// Check that output is correct.
if (referenceOutputPath != null) {
referenceOutputPath = new File(baseUri.resolve(referenceOutputPath)).getPath();
assertTrue("Reference output file does not exist: "+referenceOutputPath, new File(referenceOutputPath).exists());
assertTrue("Temporary output file does not exist: "+tempFile.getPath(), tempFile.exists());
BufferedInputStream bis1 = new BufferedInputStream(
new FileInputStream(referenceOutputPath));
BufferedInputStream bis2 = new BufferedInputStream(
new FileInputStream(tempFile.getPath()));
while (true) {
int b1 = bis1.read();
int b2 = bis2.read();
assertEquals("Outputs differ.", b1, b2);
if (b1 == -1) break;
}
}
} else {
// Should not happen.
assert false;
}
}
private static boolean isWellFormed(String path) throws Exception {
BasicCALServices cal = getCALServices();
EntryPointSpec isWellFormedEntrySpec =
EntryPointSpec.make(QualifiedName.make(ModuleName.make("Cal.Test.Experimental.Utilities.XmlParserEngine_Tests"),
"isWellFormed"));
Object result = cal.runFunction(isWellFormedEntrySpec, new Object[] { path });
assert result instanceof Boolean;
return ((Boolean)result).booleanValue();
}
private static void parseAndWriteSecondXmlCanonicalForm(String inPath, String outPath) throws Exception {
BasicCALServices cal = getCALServices();
EntryPointSpec parseAndWriteSecondXmlCanonicalFormEntrySpec =
EntryPointSpec.make(QualifiedName.make(ModuleName.make("Cal.Test.Experimental.Utilities.XmlParserEngine_Tests"),
"parseAndWriteSecondXmlCanonicalForm"));
cal.runFunction(parseAndWriteSecondXmlCanonicalFormEntrySpec, new Object[] { inPath, outPath });
}
// CAL interop.
/** Property which when set will override the default workspace file. */
private static final String WORKSPACE_FILE_PROPERTY = "xmlparser.test.workspace";
/** Location of the default XML parser workspace file. */
private static final String DEFAULT_WORKSPACE_FILE = "cal.libraries.test.cws";
/** Whether or not to use a nullary workspace. */
private static final boolean USE_NULLARY_WORKSPACE = true;
/** The default workspace client id. */
private static final String DEFAULT_WORKSPACE_CLIENT_ID = USE_NULLARY_WORKSPACE ? null : "xmlparser.test";
/**
* Rather than reinitialize the CAL service multiple times, we use the CAL test
* utilities which will cache the CAL services for us.
* @return A CAL services object for the XML parser tests.
*/
public static BasicCALServices getCALServices() {
return CALServicesTestUtilities.getCommonCALServices(WORKSPACE_FILE_PROPERTY,
DEFAULT_WORKSPACE_FILE,
DEFAULT_WORKSPACE_CLIENT_ID);
}
}