/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.test.shell.harness;
import java.io.File;
import java.io.InputStream;
import net.n3.nanoxml.IXMLElement;
import net.n3.nanoxml.IXMLParser;
import net.n3.nanoxml.StdXMLReader;
import net.n3.nanoxml.XMLParserFactory;
import org.jnode.test.shell.harness.TestSpecification.RunMode;
/**
* This class is the parser for XML test specifications.
*
* @author crawley@jnode.org
*/
public class TestSpecificationParser {
// TODO ... this code would be more robust if we replaced the element and
// attribute name strings with an enum.
/**
* Parse a test specification read from the supplied input stream. It copes with a
* XML consisting of a single 'testSpec' or a 'testSet' comprising multiple 'testSpec'
* and 'include' elements.
* @param harness the harness reference is used for callbacks to (for example) load
* included testSets
* @param in the input stream containing the specification
* @param base the base gives the 'directory' context for this stream
* @return a TestSetSpecification holding everything parsed.
* @throws Exception
*/
public TestSetSpecification parse(TestHarness harness, InputStream in, String base)
throws Exception {
StdXMLReader xr = new StdXMLReader(in);
IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
parser.setReader(xr);
TestSetSpecification res;
IXMLElement root = (IXMLElement) parser.parse();
if (root.getName().equals("testSpec")) {
res = new TestSetSpecification("", base);
res.addTestSpec(parseTestSpecification(root));
} else if (root.getName().equals("testSet")) {
String title = extractAttribute(root, "title");
res = new TestSetSpecification(title, base);
for (Object obj : root.getChildren()) {
if (obj instanceof IXMLElement) {
IXMLElement argChild = (IXMLElement) obj;
String name = argChild.getName();
if (name.equals("testSpec")) {
// (directly recursive)
res.addTestSpec(parseTestSpecification(argChild));
} else if (name.equals("plugin")) {
res.addPluginSpec(parsePluginSpecification(argChild));
} else if (name.equals("include")) {
String specName = extractAttribute(argChild, "setName");
// (indirectly recursive)
File includedFile = new File(specName);
if (!includedFile.isAbsolute()) {
includedFile = new File(base, specName);
}
TestSetSpecification included = harness.loadTestSetSpecification(includedFile);
if (included != null) {
res.addTestSetSpecification(included);
}
}
}
}
} else {
throw new TestSpecificationException(
"The root element should be 'testSpec' not '" + root.getName() + "'");
}
return res;
}
private TestSpecification parseTestSpecification(IXMLElement elem)
throws TestSpecificationException {
RunMode runMode = RunMode.valueOf(
extractAttribute(elem, "runMode", RunMode.AS_CLASS.name()));
String title = extractAttribute(elem, "title");
String command = extractAttribute(elem, "command", null);
String scriptContent = extractElementValue(elem, "script", "");
String inputContent = extractElementValue(elem, "input", "");
String outputContent = extractElementValue(elem, "output", "");
String errorContent = extractElementValue(elem, "error", "");
Class<? extends Throwable> exception;
int rc;
try {
rc = Integer.parseInt(extractAttribute(elem, "rc", "0").trim());
} catch (NumberFormatException ex) {
throw new TestSpecificationException("'rc' is not an integer");
}
try {
String className = extractAttribute(elem, "trapException", "");
if (className.length() > 0) {
Class<?> clazz = Class.forName(className);
exception = clazz.asSubclass(Throwable.class);
} else {
exception = null;
}
} catch (Throwable ex) {
throw new TestSpecificationException("'trapException' is not an exception classname");
}
if (command == null) {
if (runMode != RunMode.AS_SCRIPT) {
throw new TestSpecificationException(
"An '" + runMode + "' test requires a 'command' attribute");
} else {
command = "test";
}
}
TestSpecification res = new TestSpecification(
runMode, command, scriptContent, inputContent, outputContent, errorContent,
title, rc, exception);
for (Object obj : elem.getChildren()) {
if (obj instanceof IXMLElement) {
IXMLElement child = (IXMLElement) obj;
String name = child.getName();
if (name.equals("arg")) {
res.addArg(child.getContent());
} else if (name.equals("plugin")) {
res.addPlugin(parsePluginSpecification(child));
} else if (name.equals("file")) {
parseFile(child, res);
}
}
}
return res;
}
private PluginSpecification parsePluginSpecification(IXMLElement elem)
throws TestSpecificationException {
String pluginId = extractAttribute(elem, "id");
String pluginVersion = extractAttribute(elem, "version", "");
String pseudoPluginClassName = extractAttribute(elem, "class",
"org.jnode.test.shell.harness.DummyPseudoPlugin");
return new PluginSpecification(pluginId, pluginVersion, pseudoPluginClassName);
}
private void parseFile(IXMLElement elem, TestSpecification res)
throws TestSpecificationException {
String fileName = extractAttribute(elem, "name");
boolean isInput = extractAttribute(elem, "input", "false").equals("true");
boolean isDirectory = extractAttribute(elem, "directory", "false").equals("true");
String content = extractElementValue(elem, null, "");
File file = new File(fileName);
if (file.isAbsolute()) {
throw new TestSpecificationException(
"A '" + elem.getName() + "' element must have a relative 'name''");
}
if (isDirectory) {
if (content.length() > 0) {
throw new TestSpecificationException(
"A '" + elem.getName() + "' element for a directory cannot have content'");
}
res.addFile(new TestSpecification.FileSpecification(file, isInput));
} else {
res.addFile(new TestSpecification.FileSpecification(file, isInput, content));
}
}
@SuppressWarnings("unused")
private String extractElementValue(IXMLElement parent, String name)
throws TestSpecificationException {
IXMLElement elem = name == null ? parent : parent.getFirstChildNamed(name);
if (elem == null) {
throw new TestSpecificationException(
"Element '" + name + "' not found in '" + parent.getName() + "'");
} else {
String res = elem.getContent();
return (res == null) ? "" : res;
}
}
private String extractElementValue(IXMLElement parent, String name, String dflt) {
IXMLElement elem = name == null ? parent : parent.getFirstChildNamed(name);
String content = elem == null ? null : elem.getContent();
return content == null ? dflt : content;
}
private String extractAttribute(IXMLElement elem, String name)
throws TestSpecificationException {
String attr = elem.getAttribute(name, null);
if (attr == null) {
throw new TestSpecificationException(
"Attribute '" + name + "' not found in '" + elem.getName() + "'");
} else {
return attr;
}
}
private String extractAttribute(IXMLElement elem, String name, String dflt) {
return elem.getAttribute(name, dflt);
}
}