blob: 17f0341666e8d16ef553fafd819fc193873300df [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.harmony.xml;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathVariableResolver;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* The implementation-independent part of the <a
* href="http://jaxen.codehaus.org/">Jaxen</a> XPath test suite, adapted for use
* by JUnit. To run these tests on a device:
* <ul>
* <li>Obtain the Jaxen source from the project's website.
* <li>Copy the files to a device: <code>adb shell mkdir /data/jaxen ;
* adb push /home/dalvik-prebuild/jaxen /data/jaxen</code>
* <li>Invoke this class' main method, passing the on-device path to the test
* suite's root directory as an argument.
* </ul>
*/
public class JaxenXPathTestSuite {
private static final String DEFAULT_JAXEN_HOME = "/home/dalvik-prebuild/jaxen";
public static Test suite() throws Exception {
String jaxenHome = System.getProperty("jaxen.home", DEFAULT_JAXEN_HOME);
return suite(new File(jaxenHome));
}
/**
* Creates a test suite from the Jaxen tests.xml catalog.
*/
public static Test suite(File jaxenHome)
throws ParserConfigurationException, IOException, SAXException {
/*
* The tests.xml document has this structure:
*
* <tests>
* <document url="...">
* <context .../>
* <context .../>
* <context .../>
* </document>
* <document url="...">
* <context .../>
* </document>
* </tests>
*/
File testsXml = new File(jaxenHome + "/xml/test/tests.xml");
Element tests = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(testsXml).getDocumentElement();
TestSuite result = new TestSuite();
for (Element document : elementsOf(tests.getElementsByTagName("document"))) {
String url = document.getAttribute("url");
InputSource inputSource = new InputSource("file:" + jaxenHome + "/" + url);
for (final Element context : elementsOf(document.getElementsByTagName("context"))) {
contextToTestSuite(result, url, inputSource, context);
}
}
return result;
}
/**
* Populates the test suite with tests from the given XML context element.
*/
private static void contextToTestSuite(TestSuite suite, String url,
InputSource inputSource, Element element) {
/*
* Each context element has this structure:
*
* <context select="...">
* <test .../>
* <test .../>
* <test .../>
* <valueOf .../>
* <valueOf .../>
* <valueOf .../>
* </context>
*/
String select = element.getAttribute("select");
Context context = new Context(inputSource, url, select);
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setXPathVariableResolver(new ElementVariableResolver(element));
for (Element test : elementsOf(element.getChildNodes())) {
if (test.getTagName().equals("test")) {
suite.addTest(createFromTest(xpath, context, test));
} else if (test.getTagName().equals("valueOf")) {
suite.addTest(createFromValueOf(xpath, context, test));
} else {
throw new UnsupportedOperationException("Unsupported test: " + context);
}
}
}
/**
* Returns the test described by the given {@code <test>} element. Such
* tests come in one of three varieties:
*
* <ul>
* <li>Expected failures.
* <li>String matches. These tests have a nested {@code <valueOf>} element
* that sub-selects an expected text.
* <li>Count matches. These tests specify how many nodes are expected to
* match.
* </ul>
*/
private static TestCase createFromTest(
final XPath xpath, final Context context, final Element element) {
final String select = element.getAttribute("select");
/* Such as <test exception="true" select="..." count="0"/> */
if (element.getAttribute("exception").equals("true")) {
return new XPathTest(context, select) {
@Override void test(Node contextNode) {
try {
xpath.evaluate(select, contextNode);
fail("Expected exception!");
} catch (XPathExpressionException expected) {
}
}
};
}
/* a <test> with a nested <valueOf>, both of which have select attributes */
NodeList valueOfElements = element.getElementsByTagName("valueOf");
if (valueOfElements.getLength() == 1) {
final Element valueOf = (Element) valueOfElements.item(0);
final String valueOfSelect = valueOf.getAttribute("select");
return new XPathTest(context, select) {
@Override void test(Node contextNode) throws XPathExpressionException {
Node newContext = (Node) xpath.evaluate(
select, contextNode, XPathConstants.NODE);
assertEquals(valueOf.getTextContent(),
xpath.evaluate(valueOfSelect, newContext, XPathConstants.STRING));
}
};
}
/* Such as <test select="..." count="5"/> */
final String count = element.getAttribute("count");
if (count.length() > 0) {
return new XPathTest(context, select) {
@Override void test(Node contextNode) throws XPathExpressionException {
NodeList result = (NodeList) xpath.evaluate(
select, contextNode, XPathConstants.NODESET);
assertEquals(Integer.parseInt(count), result.getLength());
}
};
}
throw new UnsupportedOperationException("Unsupported test: " + context);
}
/**
* Returns the test described by the given {@code <valueOf>} element. These
* tests select an expected text.
*/
private static TestCase createFromValueOf(
final XPath xpath, final Context context, final Element element) {
final String select = element.getAttribute("select");
return new XPathTest(context, select) {
@Override void test(Node contextNode) throws XPathExpressionException {
assertEquals(element.getTextContent(),
xpath.evaluate(select, contextNode, XPathConstants.STRING));
}
};
}
/**
* The subject of an XPath query. This is itself defined by an XPath query,
* so each test requires at least XPath expressions to be evaluated.
*/
static class Context {
private final InputSource inputSource;
private final String url;
private final String select;
Context(InputSource inputSource, String url, String select) {
this.inputSource = inputSource;
this.url = url;
this.select = select;
}
Node getNode() {
XPath xpath = XPathFactory.newInstance().newXPath();
try {
return (Node) xpath.evaluate(select, inputSource, XPathConstants.NODE);
} catch (XPathExpressionException e) {
Error error = new AssertionFailedError("Failed to get context");
error.initCause(e);
throw error;
}
}
@Override public String toString() {
return url + " " + select;
}
}
/**
* This test evaluates an XPath expression against a context node and
* compares the result to a known expectation.
*/
public abstract static class XPathTest extends TestCase {
private final Context context;
private final String select;
public XPathTest(Context context, String select) {
super("test");
this.context = context;
this.select = select;
}
abstract void test(Node contextNode) throws XPathExpressionException;
public final void test() throws XPathExpressionException {
try {
test(context.getNode());
} catch (XPathExpressionException e) {
if (isMissingFunction(e)) {
fail(e.getCause().getMessage());
} else {
throw e;
}
}
}
private boolean isMissingFunction(XPathExpressionException e) {
return e.getCause() != null
&& e.getCause().getMessage().startsWith("Could not find function");
}
@Override public String getName() {
return context + " " + select;
}
}
/**
* Performs XPath variable resolution by using {@code var:name="value"}
* attributes from the given element.
*/
private static class ElementVariableResolver implements XPathVariableResolver {
private final Element element;
public ElementVariableResolver(Element element) {
this.element = element;
}
public Object resolveVariable(QName variableName) {
return element.getAttribute("var:" + variableName.getLocalPart());
}
}
private static List<Element> elementsOf(NodeList nodeList) {
List<Element> result = new ArrayList<Element>();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element) {
result.add((Element) node);
}
}
return result;
}
}