package test;
import com.saxonica.config.EnterpriseConfiguration;
import net.sf.saxon.Configuration;
import net.sf.saxon.FeatureKeys;
import net.sf.saxon.StandardErrorListener;
import net.sf.saxon.Version;
import net.sf.saxon.event.*;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NameTest;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.SchemaManager;
import net.sf.saxon.s9api.SchemaValidator;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.SchemaException;
import net.sf.saxon.type.Type;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;
import java.util.regex.Pattern;
/**
* This class runs the W3C XML Schema Test Suite, driven from the test catalog.
*/
public class SchemaTestSuiteDriver {
public final static String testNS = "http://www.w3.org/XML/2004/xml-schema-test-suite/";
public final static String SCM_SCHEMA_LOCATION = "c:/MyJava/samples/scm/scmschema.scm";
/**
* Run the testsuite using Saxon.
*
* @param args Array of parameters passed to the application
* via the command line.
*/
public static void main(String[] args) throws Exception {
if (args.length == 0 || args[0].equals("-?")) {
System.err.println("SchemaTestSuiteDriver testDir [-w] [-onwards] [-scm] -c:contributor? -s:setName? -g:groupName?");
}
System.err.println("Testing Saxon " + Version.getProductVersion());
new SchemaTestSuiteDriver().go(args);
}
String testSuiteDir;
EnterpriseConfiguration catalogConfig;
SchemaValidator scmValidator;
XMLReader parser;
boolean showWarnings = true;
boolean onwards = false;
boolean useSCM = false;
Writer results;
int xlinkHref;
private NameTest elementNameTest(NamePool pool, String local) {
int nameFP = pool.allocate("", testNS, local) & NamePool.FP_MASK;
return new NameTest(Type.ELEMENT, nameFP, pool);
}
private NodeInfo getChildElement(NodeInfo parent, NameTest child) {
return (NodeInfo)parent.iterateAxis(Axis.CHILD, child).next();
}
private DocumentInfo getLinkedDocument(NodeInfo element, EnterpriseConfiguration targetConfig, boolean validate)
throws URISyntaxException, XPathException {
ParseOptions options = new ParseOptions();
String href = element.getAttributeValue(xlinkHref);
URI target = new URI(element.getBaseURI()).resolve(href);
Source ss = new StreamSource(target.toString());
options.setLineNumbering(true);
if (validate) {
PipelineConfiguration pipe = targetConfig.makePipelineConfiguration();
options.setSchemaValidationMode(Validation.STRICT | Validation.VALIDATE_OUTPUT);
options.setContinueAfterValidationErrors(true);
new Sender(pipe).send(ss, new Sink(), options);
return null;
}
return targetConfig.buildDocument(ss);
}
/**
* Run the tests
* @param args command line arguments
* @throws SAXException
* @throws ParserConfigurationException
* @throws XPathException
* @throws IOException
* @throws URISyntaxException
*/
public void go(String[] args) throws SAXException, ParserConfigurationException,
XPathException, IOException, URISyntaxException {
testSuiteDir = args[0];
Pattern testSetPattern = null;
Pattern testGroupPattern = null;
String contributor = null;
HashMap exceptions = new HashMap();
for (int i=1; i<args.length; i++) {
if (args[i].equals("-w")) {
showWarnings = true;
} else if (args[i].equals("-onwards")) {
onwards = true;
} else if (args[i].equals("-scm")) {
useSCM = true;
try {
Processor scmProcessor = new Processor(true);
SchemaManager sm = scmProcessor.getSchemaManager();
sm.importComponents(new StreamSource(new File(SCM_SCHEMA_LOCATION)));
scmValidator = sm.newSchemaValidator();
} catch (SaxonApiException e) {
e.printStackTrace();
useSCM = false;
}
} else if (args[i].startsWith("-c:")) {
contributor = args[i].substring(3);
} else if (args[i].startsWith("-s:")) {
testSetPattern = Pattern.compile(args[i].substring(3));
} else if (args[i].startsWith("-g:")) {
testGroupPattern = Pattern.compile(args[i].substring(3));
} else if (args[i].equals("-?")) {
System.err.println("Usage: SchemaTestSuiteDriver testDir [-w] [-s:testSetPattern] [-g:testGroupPattern]");
}
}
try {
NamePool pool = new NamePool();
catalogConfig = new EnterpriseConfiguration();
catalogConfig.setNamePool(pool);
catalogConfig.setHostLanguage(Configuration.XML_SCHEMA);
parser = catalogConfig.getSourceParser();
xlinkHref = pool.allocate("", "http://www.w3.org/1999/xlink", "href");
int testCaseFP = pool.allocate("", "", "testcase") & NamePool.FP_MASK;
NameTest testCaseNT = new NameTest(Type.ELEMENT, testCaseFP, pool);
int commentFP = pool.allocate("", "", "comment") & NamePool.FP_MASK;
NameTest commentNT = new NameTest(Type.ELEMENT, commentFP, pool);
NameTest testSetRefNT = elementNameTest(pool, "testSetRef");
NameTest testGroupNT = elementNameTest(pool, "testGroup");
NameTest testSetNT = elementNameTest(pool, "testSet");
NameTest schemaTestNT = elementNameTest(pool, "schemaTest");
NameTest instanceTestNT = elementNameTest(pool, "instanceTest");
NameTest schemaDocumentNT = elementNameTest(pool, "schemaDocument");
NameTest instanceDocumentNT = elementNameTest(pool, "instanceDocument");
NameTest expectedNT = elementNameTest(pool, "expected");
NameTest currentNT = elementNameTest(pool, "current");
int validityAtt = pool.allocate("", "", "validity") & NamePool.FP_MASK;
int nameAtt = pool.allocate("", "", "name") & NamePool.FP_MASK;
int contributorAtt = pool.allocate("", "", "contributor") & NamePool.FP_MASK;
int setAtt = pool.allocate("", "", "set") & NamePool.FP_MASK;
int groupAtt = pool.allocate("", "", "group") & NamePool.FP_MASK;
int statusAtt = pool.allocate("", "", "status") & NamePool.FP_MASK;
int bugzillaAtt = pool.allocate("", "", "bugzilla") & NamePool.FP_MASK;
int targetNamespaceAtt = pool.allocate("", "", "targetNamespace") & NamePool.FP_MASK;
int schemaVersionAtt = pool.allocate("saxon", NamespaceConstant.SAXON, "schemaVersion") & NamePool.FP_MASK;
DocumentInfo catalog = catalogConfig.buildDocument(
new StreamSource(new File(testSuiteDir + "/suite.xml"))
);
results = new BufferedWriter(new FileWriter(new File(testSuiteDir + "/SaxonResults"
+ Version.getProductVersion() + ".xml")));
results.write("<testSuiteResults xmlns='" + testNS + "' xmlns:saxon='http://saxon.sf.net/' " +
"suite='TS_2006' " +
"processor='Saxon-EE (Java) 9.2' submitDate='2007-01-05' publicationPermission='public'>\n");
DocumentInfo exceptionsDoc = catalogConfig.buildDocument(
new StreamSource(new File(testSuiteDir + "/exceptions.xml"))
);
AxisIterator exceptionTestCases = exceptionsDoc.iterateAxis(Axis.DESCENDANT, testCaseNT);
while (true) {
NodeInfo testCase = (NodeInfo)exceptionTestCases.next();
if (testCase == null) {
break;
}
String set = testCase.getAttributeValue(setAtt);
String group = testCase.getAttributeValue(groupAtt);
String comment = getChildElement(testCase, commentNT).getStringValue();
exceptions.put(set + "#" + group, comment);
}
long startTime = (new Date()).getTime();
AxisIterator testSets = catalog.iterateAxis(Axis.DESCENDANT, testSetRefNT);
while (true) {
NodeInfo testSetRef = (NodeInfo)testSets.next();
if (testSetRef == null) {
break;
}
DocumentInfo testSetDoc = getLinkedDocument(testSetRef, catalogConfig, false);
NodeInfo testSetElement = getChildElement(testSetDoc, testSetNT);
if (testSetElement == null) {
System.err.println("test set doc has no TestSet child: " + testSetDoc.getBaseURI());
continue;
}
String testSetName = testSetElement.getAttributeValue(nameAtt);
System.err.println("Test set " + testSetName);
if (testSetPattern != null && !testSetPattern.matcher(testSetName).matches()) {
continue;
}
if (contributor != null && !contributor.equals(testSetElement.getAttributeValue(contributorAtt))) {
continue;
}
boolean allow11throughout = "1.1".equals(testSetElement.getAttributeValue(schemaVersionAtt));
AxisIterator testGroups = testSetElement.iterateAxis(Axis.CHILD, testGroupNT);
while (true) {
NodeInfo testGroup = (NodeInfo)testGroups.next();
if (testGroup == null) {
break;
}
boolean allow11here = "1.1".equals(testGroup.getAttributeValue(schemaVersionAtt));
String testGroupName = testGroup.getAttributeValue(nameAtt);
String exception = (String)exceptions.get(testSetName + "#" + testGroupName);
if (testGroupPattern != null && !testGroupPattern.matcher(testGroupName).matches()) {
continue;
}
System.err.println("TEST SET " + testSetName + " GROUP " + testGroupName);
if (onwards) {
testGroupPattern = null;
testSetPattern = null;
}
EnterpriseConfiguration testConfig = new EnterpriseConfiguration();
testConfig.setHostLanguage(Configuration.XML_SCHEMA);
testConfig.setValidationWarnings(true);
if (allow11throughout || allow11here) {
testConfig.setConfigurationProperty(FeatureKeys.XSD_VERSION, "1.1");
}
AxisIterator schemaTests = testGroup.iterateAxis(Axis.CHILD, schemaTestNT);
boolean schemaQueried = false;
String bugzillaRef = null;
while (true) {
NodeInfo schemaTest = (NodeInfo)schemaTests.next();
if (schemaTest == null) {
break;
}
bugzillaRef = null;
String testName = schemaTest.getAttributeValue(nameAtt);
if (exception != null) {
results.write("<testResult set='" + testSetName +
"' group='" + testGroupName +
"' test='" + testName +
"' actualValidity='notKnown' saxon:outcome='notRun' saxon:comment='" + exception +
"'/>\n");
continue;
}
boolean queried = false;
NodeInfo statusElement = getChildElement(schemaTest, currentNT);
if (statusElement != null) {
String status = statusElement.getAttributeValue(statusAtt);
queried = "queried".equals(status);
bugzillaRef = statusElement.getAttributeValue(bugzillaAtt);
}
if (queried) {
schemaQueried = true;
}
System.err.println("TEST SCHEMA " + testName + (queried ? " (queried)" : ""));
boolean success = true;
success = loadSchemas(schemaDocumentNT, targetNamespaceAtt, testConfig, schemaTest, success);
NodeInfo expected = getChildElement(schemaTest, expectedNT);
boolean expectedSuccess = expected==null ||
expected.getAttributeValue(validityAtt).equals("valid");
results.write("<testResult set='" + testSetName +
"' group='" + testGroupName +
"' test='" + testName +
"' actualValidity='" + (success ? "valid" : "invalid" ) +
"' expectedValidity='" + (expectedSuccess ? "valid" : "invalid" ) +
(queried ? "' saxon:queried='true' saxon:bugzilla='" + bugzillaRef : "") +
"' saxon:outcome='" + (success==expectedSuccess ? "same" : "different") +
"'/>\n");
}
AxisIterator instanceTests = testGroup.iterateAxis(Axis.CHILD, instanceTestNT);
while (true) {
NodeInfo instanceTest = (NodeInfo)instanceTests.next();
if (instanceTest == null) {
break;
}
String testName = instanceTest.getAttributeValue(nameAtt);
if (exception != null) {
results.write("<testResult set='" + testSetName +
"' group='" + testGroupName +
"' test='" + testName +
"' actualValidity='notKnown' saxon:outcome='notRun' saxon:comment='" + exception +
"'/>\n");
continue;
}
boolean queried = false;
NodeInfo statusElement = getChildElement(instanceTest, currentNT);
if (statusElement != null) {
String status = statusElement.getAttributeValue(statusAtt);
queried = "queried".equals(status);
String instanceBug = statusElement.getAttributeValue(bugzillaAtt);
if (instanceBug != null) {
bugzillaRef = instanceBug;
}
}
queried |= schemaQueried;
System.err.println("TEST INSTANCE " + testName + (queried ? " (queried)" : ""));
NodeInfo instanceDocument = getChildElement(instanceTest, instanceDocumentNT);
boolean success = true;
try {
DocumentInfo instanceDoc = getLinkedDocument(instanceDocument, testConfig, true);
} catch (XPathException err) {
System.err.println(err.getMessage());
success = false;
}
NodeInfo expected = getChildElement(instanceTest, expectedNT);
boolean expectedSuccess = expected==null ||
expected.getAttributeValue(validityAtt).equals("valid");
results.write("<testResult set='" + testSetName +
"' group='" + testGroupName +
"' test='" + testName +
"' actualValidity='" + (success ? "valid" : "invalid" ) +
"' expectedValidity='" + (expectedSuccess ? "valid" : "invalid" ) +
(queried ? "' saxon:queried='true' saxon:bugzilla='" + bugzillaRef : "") +
"' saxon:outcome='" + (success==expectedSuccess ? "same" : "different") +
"'/>\n");
}
}
}
results.write("</testSuiteResults>");
results.close();
System.err.println("Time: " + (new Date().getTime() - startTime) + " ms");
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean loadSchemas(NameTest schemaDocumentNT, int targetNamespaceAtt, EnterpriseConfiguration testConfig, NodeInfo schemaTest, boolean success) throws URISyntaxException, XPathException {
if (useSCM) {
return loadSchemasViaSCM(schemaDocumentNT, targetNamespaceAtt, testConfig, schemaTest, success);
} else {
return loadSchemasDirect(schemaDocumentNT, targetNamespaceAtt, testConfig, schemaTest, success);
}
}
private boolean loadSchemasDirect(NameTest schemaDocumentNT, int targetNamespaceAtt, EnterpriseConfiguration testConfig, NodeInfo schemaTest, boolean success)
throws XPathException, URISyntaxException {
AxisIterator schemata = schemaTest.iterateAxis(Axis.CHILD, schemaDocumentNT);
while (true) {
NodeInfo schemaDocumentRef = (NodeInfo)schemata.next();
if (schemaDocumentRef == null) {
break;
}
System.err.println("Loading schema at " + schemaDocumentRef.getAttributeValue(xlinkHref));
DocumentInfo schemaDoc = getLinkedDocument(schemaDocumentRef, testConfig, false);
NodeInfo schemaElement =
(NodeInfo)schemaDoc.iterateAxis(Axis.CHILD, NodeKindTest.ELEMENT).next();
String targetNamespace = schemaElement.getAttributeValue(targetNamespaceAtt);
if (targetNamespace != null && testConfig.isSchemaAvailable(targetNamespace)) {
// do nothing
// TODO: this is the only way I can get MS additional test addB132 to work.
// This test has two schema documents, A and B; A imports B, and the test catalog
// requests both A and B to be loaded.
// It's not ideal: addSchemaSource() ought to be a no-op if the schema components
// are already loaded, but in fact recompiling the imported schema document on its
// own is losing the substitution group membership that was defined in the
// importing document.
} else {
try {
testConfig.addSchemaSource(schemaDoc); // TODO: use an ErrorListener
} catch (SchemaException err) {
success = false;
}
}
}
return success;
}
private boolean loadSchemasViaSCM(NameTest schemaDocumentNT, int targetNamespaceAtt, EnterpriseConfiguration testConfig, NodeInfo schemaTest, boolean success)
throws XPathException, URISyntaxException {
EnterpriseConfiguration tempConfig = new EnterpriseConfiguration();
String xsdVersion = (String)testConfig.getConfigurationProperty(FeatureKeys.XSD_VERSION);
tempConfig.setConfigurationProperty(FeatureKeys.XSD_VERSION, xsdVersion);
AxisIterator schemata = schemaTest.iterateAxis(Axis.CHILD, schemaDocumentNT);
while (true) {
NodeInfo schemaDocumentRef = (NodeInfo)schemata.next();
if (schemaDocumentRef == null) {
break;
}
System.err.println("Loading schema at " + schemaDocumentRef.getAttributeValue(xlinkHref));
DocumentInfo schemaDoc = getLinkedDocument(schemaDocumentRef, testConfig, false);
NodeInfo schemaElement =
(NodeInfo)schemaDoc.iterateAxis(Axis.CHILD, NodeKindTest.ELEMENT).next();
String targetNamespace = schemaElement.getAttributeValue(targetNamespaceAtt);
if (targetNamespace != null && tempConfig.isSchemaAvailable(targetNamespace)) {
// do nothing
} else {
try {
tempConfig.addSchemaSource(schemaDoc); // TODO: use an ErrorListener
} catch (SchemaException err) {
return false;
}
}
}
try {
StringWriter sw = new StringWriter();
Result result = new StreamResult(sw);
Receiver serializer = tempConfig.getSerializerFactory().getReceiver(
result, tempConfig.makePipelineConfiguration(), new Properties());
tempConfig.exportComponents(serializer);
String scm = sw.toString();
try {
scmValidator.validate(new StreamSource(new StringReader(scm)));
} catch (SaxonApiException e) {
System.err.println("*** SCM does not validate against schema for SCM ***");
System.err.println(scm);
System.exit(2);
}
try {
testConfig.importComponents(new StreamSource(new StringReader(scm)));
} catch (XPathException e) {
e.printStackTrace();
System.exit(2);
}
} catch (XPathException e) {
if (!e.hasBeenReported()) {
System.err.println(e.getMessage());
}
return false;
}
return true;
}
protected String getResultDirectoryName() {
return "SaxonResults";
}
protected boolean isExcluded(String testName) {
return false;
}
private class MyErrorListener extends StandardErrorListener {
public String errorCode;
/**
* Receive notification of a recoverable error.
*/
public void error(TransformerException exception) throws TransformerException {
if (exception instanceof XPathException) {
String code = ((XPathException)exception).getErrorCodeLocalPart();
if (code != null) {
errorCode = code;
}
if ("FODC0005".equals(errorCode)) {
fatalError(exception);
}
}
super.error(exception);
}
/**
* Receive notification of a non-recoverable error.
*/
public void fatalError(TransformerException exception) throws TransformerException {
if (exception instanceof XPathException) {
String code = ((XPathException)exception).getErrorCodeLocalPart();
if (code != null) {
errorCode = code;
}
}
super.fatalError(exception);
}
/**
* Receive notification of a warning.
*/
public void warning(TransformerException exception) throws TransformerException {
if (showWarnings) {
super.warning(exception);
}
}
/**
* Make a clean copy of this ErrorListener. This is necessary because the
* standard error listener is stateful (it remembers how many errors there have been)
*/
public StandardErrorListener makeAnother(int hostLanguage) {
return new MyErrorListener();
}
}
}